redis要点总结
redis与memcache区别:
- redis数据类型丰富,memcache只有基本的键值存储
- redis可持久化,memcache不能
- memcache较快
redis优点:快,数据类型丰富,可持久化
- String(SDS:simple dynamic string): 字符串 set 和 get,做简单的 KV 缓存
- <=44:embstr >44:raw
- List(ziplist(数量小于512),双向链表): 列表 粉丝列表、文章的评论列表、分页(lrange);消息队列
- Hash(ziplist(数量小于512),哈希表): 散列 可以存放结构化的数据为值
- 扩容缩容时采用渐进式哈希
- Set(哈希表): 集合 自动去重;适用于集合间的运算(如两人好友列表的交集)
- Sorted Set(ziplist(元素小于128),跳表): 有序集合 自动去重、排序 适用于排行榜
附:
哈希表渐进式 rehash 的详细步骤:
(1)为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。
(2)在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。
(3)在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。
(4)随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。
渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。
主要用途:
- 缓存
- 任务队列(list):阻塞队列 rpush,blpop;异步队列 rpush,lpop ; “发布/订阅”模式(消费者下线的情况下,生产的消息会丢失)
- 分布式锁(setnx设置 del删除 先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放) 事务
- 位图(setbit)可用于布隆过滤器(2^32大小,对应到内存也就是512MB)
使用场景:
- 业务数据常用吗?命中率如何? 如果命中率很低,就没有必要写入缓存;
- 该业务数据是读操作多,还是写操作多? 如果写操作多,频繁需要写入数据库,也没有必要使用缓存;
- 业务数据大小如何? 如果要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没有必要;
部分命令:
使用keys指令可以扫出指定模式的key列表
OBJECT ENCODING key获得key的底层数据类型
雪崩: key在同一时间失效,请求全部落到数据库
解决:将每个key的失效时间设置为随机值 setRedis(Key,value,time + Math.random() * 10000); 穿透: 缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。
解决:对请求参数校验;将对应Key的Value设置为null;布隆过滤器 击穿: 类似与雪崩,雪崩是关于大面积key失效,而击穿是对于一个热点key失效
解决:
-
互斥锁:互斥锁简单来说就是在Redis中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则睡眠一段时间(比如100ms)后重试。
-
设置热点数据永远不过期,定时更新;
-
对数据库查询限流(令牌桶、漏桶)
-
降级,直接返回NULL
**缓存一致性:**数据库更新时对应的缓存也应该更新(更新不采用更新的方式,而是删除对应缓存。理由是如果数据库频繁更新,那么缓存也要频繁更新,而删除缓存会只在请求访问的时候更新一次)
- 先更新数据库,再删除缓存。先更新DB数据,然后通过发送操作缓存的消息到消息队列,进行更新缓存操作,这里还需要的一个操作就是利用消息队列的重试机制,保证缓存能够更新成功
- 先删除缓存,再更新数据库。解决方案是使用延迟双删。
redis为什么快:
1. 在内存中,不用读写磁盘;
2. 事件分发器单线程,避免了锁和上下文切换;
3. 使用IO多路复用(select、epoll),非阻塞IO;
4. c语言编写比较较快;
Redis的过期策略,是有**定期删除(定时主动检查)+惰性删除(被查询时才检查)**两种。
内存淘汰机制:
noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。默认策略 allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。 allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。 volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。 volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。 volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
zset有序集合为什么用跳表不用红黑树: 实现简单;每次插入红黑树需要平衡节点,跳表只涉及局部;区间查询效率高;
集群的部署方式也就是Redis cluster,并且是主从同步读写分离,类似Mysql的主从同步,Redis cluster 支撑 N 个 Redis master node,每个master node都可以挂载多个 slave node。
持久化:
-
RDB:RDB 持久化机制,是对 Redis 中的数据执行周期性的持久化。
优:体积相对于AOF要小,恢复要快
缺:同步间隔较大,适合冷备份
-
AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中。 AOF文件重写:当AOF文件达到一定体积时,恢复数据时会很慢,所以重写指令(如将4条add指令合为一条)到文件以减少体积。 AOF重写缓冲区:因为子进程在进行AOF重写期间(时间较长),服务器进程依然在处理其它命令,这新的命令有可能也对数据库进行了修改操作,使得当前数据库状态和重写后的 AOF 文件状态不一致。这个缓冲区是在创建子进程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到 AOF 重写缓冲区。当子进程完成 AOF 重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将 AOF 重写缓冲区的内容都写到新的 AOF文件中。
优:同步间隔小
缺:性能耗费高,文件体积大,数据恢复较慢
redis四种模式:
-
单机模式:优点:简单 缺点:可靠性低,吞吐量低
-
主从模式:优点:读写分离,吞吐量提高 缺点:master单点故障,需人工修改master节点
-
哨兵模式:主节点故障时,主从可以自动切换
-
集群模式:解决了单节点存储能力上限问题。方式:设置虚拟分区(类似一致性哈希的虚拟节点),通过哈希分配到不同节点上
- raft在redis集群的leader选举中有很好地应用
Redis采用单线程模型,通过IO多路复用来监听多个连接,非阻塞IO
Redis同时处理文件事件和时间事件
- 文件事件,Redis将产生事件套接字放入一个
队列中,然后依次分派给文件事件处理器 - 时间事件包含
定时事件和周期性事件,Redis将其放入一个无序链表中,每当时间事件执行器运行时,就遍历链表,查找已经到达的时间事件,调用相应的处理器。
Redis事务处理机制
首先 Redis 不支持事务的回滚机制(Rollback),这也就意味着当事务发生了错误(只要不是语法错误),整个事务依然会继续执行下去,直到事务队列中所有命令都执行完毕。
Redis 的事务处理命令:
- MULTI:开启一个事务;
- EXEC:事务执行,将一次性执行事务内的所有命令;
- DISCARD:取消事务;
- WATCH:监视一个或多个键,如果事务执行前某个键发生了改动,那么事务也会被打断;
- UNWATCH:取消 WATCH 命令对所有键的监视
我们经常使用 Redis 的 WATCH 和 MULTI 命令来处理共享资源的并发操作,比如秒杀,抢票等。实际上 WATCH+MULTI 实现的是乐观锁