Redis系统篇
从系统维度介绍Redis的常见面试题。参考:https://zhuanlan.zhihu.com/p/427496556
基本数据类型与应用场景
- 缓存
- 排行榜
- 计数器应用
- 共享Session
- 分布式锁
- 消息队列
- 位操作
string类
应用场景:缓存(共享session)、分布式锁,计数器、限流。
哈希类
应用场景:缓存用户信息等。
列表
1 | lpush+lpop=Stack(栈) |
应用场景:消息队列
集合
应用场景:用户标签、共同好友
有序集合
应用场景:排行榜
Redis为什么快?
基于内存
redis是纯内存操作:数据存放在内存中,内存的响应时间大约是100纳秒,这是Redis每秒万亿级别访问的重要基础。
非阻塞I/O:Redis采用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间。
单线程机制
Redis用单线程处理客户端请求,避免了线程切换和竞争态消耗。
Redis基于Reactor模式开发了自己的网络事件处理器,由以下这四部分组成。
- 套接字;
- I/O多路复用程序;
- 文件事件分派器(dispatcher);
- 事件处理器
IO多路复用思想是:让内核遍历socker是否就绪,而不是线程sleep+遍历+非阻塞套接字。
底层数据结构
Redis支持多种复杂数据结构,如字符串、列表、集合、有序集合和哈希表等。这些数据结构在某些场景下能够显著提高性能。
过期删除策略
过期校验
首先,Redis维护了一个过期字典,expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。
任何对key的查询都要先查过期字典,如果不在过期字典中,说明是持久数据(没有设置过期时间的数据)。
如果在字典中,就校验是否过期,没过期才继续查询。
过期删除
Redis维护了两种过期删除策略,实际上这两种过期策略都有使用。
- 惰性删除(被动删除)
- 定期删除(主动删除)
(1)惰性删除
一个key只有被访问时才去判断是否已经过期,如果过期了,就删除它。这是一种被动删除的策略。
优点:在执行查询时才检查key是否过期,CPU友好
缺点:存在未访问的过期key堆积现象,内存得不到有效清理(内存不友好)
(2)定期删除
Redis每隔100ms就随机抽取一定数量的key进行过期检查和删除。如果过期的key占比全部抽样key超过25%,那么Redis就会继续进行抽样检测。同时为了避免抽样检测的无限进行,设置了一个最长任务时间。
优点:较为平衡的策略,cpu压力低同时也能执行删除策略。
缺点:定期任务的频率和时长难以控制。频率高则CPU压力大,时长低则删除不彻底。
内存淘汰策略
当Redis的运行内存达到max_memory,就会触发内存淘汰机制。此时内存不足以容纳新数据的写入。
内存淘汰主要可分为三大类,共8种:
- 不进行内存淘汰,直接报错服务不可用;
- 针对设置过期时间的key的内存淘汰策略;
- 针对没有设置过期时间的key的内存淘汰策略。
后两者差不多,只不过针对的key不同。$4+3+1 = 8$
1 | ttl:淘汰最早过期时间的key |
LRU
least Recently Used ,Recently Used 即最近使用的,least最少的。从时间的角度上淘汰key,即淘汰距离上一次使用时间最长的key。
LRU算法的思想是:如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最久没有访问的数据最先被置换(淘汰)。
实现:我们可以用双向链表(LinkedList)+哈希表(HashMap)实现(链表用来表示位置,哈希表用来存储和查找),在Java里有对应的数据结构LinkedHashMap。
1 | LRU算法的描述: 设计一种缓存结构,该结构在构造时确定大小,假设大小为 K,并有两个功能: |
LFU
LFU(Least Frequently Used ,最近最少使用算法),从使用次数的角度上淘汰key,即淘汰最少使用次数的数据。
LFU的每个数据块都有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排序。
实现:考虑到 LFU 会淘汰访问使用次数最小的数据,即选出使用次数最小的元素,最终实现策略为小顶堆+哈希表。
1 | LFU 算法的描述: |
持久化机制
redis是一个内存数据库,当Redis服务重启时,在内存中的数据就会丢失。所以需要定期将内存中的数据同步到硬盘文件来保证数据的持久化。当Redis重启时,通过加载硬盘文件到内存,就能达到恢复数据的目的。
redis主要有两种持久化的手段:
- AOF (append only file, 只追加文件)
- RDB (Redis DataBase, 基于Redis数据库)
AOF
(1)概念
AOF就是append only file, 只追加文件。通过日志的方式记录Redis的每个“写”命令。
在Redis服务重启时,重新加载执行日志文件中的命令,从而达到恢复数据的效果。
(2)应用场景
优点:稳定、数据完整性好;
适合场景:数据安全性要求高(订单、购物车)
缺点:AOF文件会越来越大,比较冗余
RDB
(1)概念
RDB就是Redis Databse,基于Redis的数据库文件。这是指在一定的时间间隔内,将内存中的键值对数据以数据集快照的形式写入磁盘。也就是保存某个时间点的快照,这个快照文件就是RDB。
Redis定时执行一个子进程,这个子进程将数据写到RDB临时文件,等待临时文件写完,就将临时文件替换RDB文件。
(2)应用场景
优点:恢复大数据集速度比AOF快
适合场景:数据备份
缺点:基于数据备份的方式会丢失上次备份与服务宕机时间点之间的数据
实际 使用通常都是结合两者来。
Redis集群/高可用
为什么要集群?因为单台服务器部署的Redis服务不能满足高并发压力,且服务稳定性也无法得到保障(单服务器挂之后,整个系统可能就会崩溃)。
主从模式
主从模式,就是一台Redis服务器作为主服务器,同时有若干台服务器作为从服务器。主从服务器通常采取读写分离的策略,主服务器可以进行读写操作,从服务器只读。
也就是说:数据修改只在主服务器上,然后主服务器将最新数据同步给从服务器。从节点的数据来自主节点,实现原理就是主从复制机制。主从复制包括全量复制,增量复制两种。
(1)全量复制
一般当slave第一次启动连接master,或者认为是第一次连接,就采用全量复制。
1 | 1.slave发送sync命令到master。 |
(2)增量复制
slave与master同步之后,master上的数据,如果再次发生更新,就会触发增量复制。
master节点会判断用户执行的命令是否有数据更新,如果有数据更新的话,并且slave节点不为空,就会执行此函数。这个函数作用就是:把用户执行的命令发送到所有的slave节点,让slave节点执行。
(3)主从模式存在的问题
- 数据不一致
客户端可能读到落后的副本,进而读到不一致的数据。
- 容错
主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。
哨兵模式
哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的Redis主节点和从节点,并在被监视的主节点进入下线状态时,自动将下线主服务器属下的某个从节点升级为新的主节点。
但是呢,一个哨兵进程对Redis节点进行监控,就可能会出现问题(单点问题),因此,可以使用多个哨兵来进行监控Redis节点,并且各个哨兵之间还会进行监控。
哨兵监测到主节点宕机,会自动将从节点切换成主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机;
哨兵之间还会相互监控。
Cluster集群
Cluster集群实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它也提供复制和故障转移的功能。
Hash Slot插槽算法
使用的哈希映射也比较简单,用CRC16算法计算出一个16 位的值,再对16384($2^{14}$)取模。
集群中的每个节点负责一部分的hash槽,比如当前集群有A、B、C个节点,每个节点上的哈希槽数 =16384/3,那么就有:
- 节点A负责0~5460号哈希槽
- 节点B负责5461~10922号哈希槽
- 节点C负责10923~16383号哈希槽
Redis 大Key
通常以Key的大小和Key中成员的数量来综合判定大Key:
- Key本身的数据量过大:一个String类型的Key,它的值为5 MB。
- Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10,000个。
- Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1,000个但这些成员的Value(值)总大小为100 MB。
大Key带来的常见问题
- 客户端执行命令的时长变慢。
- Redis内存达到maxmemory参数定义的上限引发操作阻塞或重要的Key被逐出,甚至引发内存溢出(Out Of Memory)。
- 集群架构下,某个数据分片的内存使用率远超其他数据分片,无法使数据分片的内存资源达到均衡。
- 对大Key执行读请求,会使Redis实例的带宽使用率被占满,导致自身服务变慢,同时易波及相关的服务。
- 对大Key执行删除操作,易造成主库较长时间的阻塞,进而可能引发同步中断或主从切换。
大key的常见处理办法:
string类型 压缩处理 进行序列化压缩,代价是反序列化读取;
list/set类型 分片处理 进行分片、拆分;
对大Key进行清理(删除)
将不适用Redis能力的数据存至其它存储,并在Redis中删除此类数据。
说明
- Redis 4.0及之后版本:您可以通过UNLINK命令安全地删除大Key甚至特大Key,该命令能够以非阻塞的方式,逐步地清理传入的Key。
- Redis 4.0之前的版本:建议先通过SCAN命令读取部分数据,然后进行删除,避免一次性删除大量key导致Redis阻塞。
Redis 热点Key
阿里云手册关于热点key问题的链接
什么是热Key呢?在Redis中,我们把访问频率高的key,称为热点key。
而热点Key是怎么产生的呢?主要原因有两个:
用户消费的数据远大于生产的数据:如秒杀、热点新闻等读多写少的场景。
请求分片集中,超过单Redi服务器的性能:比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题。
怎么解决热点key问题?
在Redis集群架构中对热Key进行复制,将热点数据分散存储在多个Redis节点上
例如将热Key foo复制出3个内容完全一样的Key并名为foo2、foo3、foo4,将这三个Key迁移到其他数据分片来解决单个数据分片的热Key压力。
该方案的缺点在于需要联动修改代码,同时带来了数据一致性的挑战(由原来更新一个Key演变为需要更新多个Key),仅建议该方案用来解决临时棘手的问题。
使用二级缓存(即JVM本地缓存+Redis缓存,减少Redis读请求)
限制流量
限流是通过控制请求的速率来防止系统过载。在应用层实现限流,可以有效减轻热点Key对Redis的压力。常见的限流算法有漏桶算法和令牌桶算法。