金三银四跳槽季已经过去,还是有好多小伙伴没有找到心仪的工作。今天我来助力一把,送出这套redis面试题,助力大家通关。
1 redis为什么响应快
1.1数据保存在内存中
redis
数据保存在内存中,读写操作只要访问内存,不需要磁盘IO
。
1.2.底层数据结构
-
redis
的数据以key:value
的格式存储在散列表中,时间复杂度o(1)
。 -
redis
为value
定义了丰富的数据结构,包括动态字符串、双向链表、压缩列表、hash
、跳表和整数数组,可以根据value的特性选择选择最高效的数据结构。
1.3.单线程模型
redis
的网络IO
和数据读写使用单线程模型,可以绑定CPU
,这避免了线程上下文切换带来的开销。
「注意:redis6.0对网络请求引入了多线程模型,读写操作还是用单线程。」
redis
多线程网络模型见下图:
1.4.IO多路复用
redis
采用epoll
网络模型,如下图:
内核会一直监听新的socket
连接事件的和已建立socket
连接的读写事件,把监听到的事件放到事件队列,redis使用单线程不停的处理这个事件队列。这避免了阻塞等待连接和读写事件到来。
这些事件绑定了回调函数,会调用redis的处理函数进行处理。
2 redis底层数据结构
redis
有5种数据类型,包括「字符串、列表、集合、有序集合和字典」。
redis
底层的数据结构有6种,包括「动态字符串、双向链表、压缩列表(ziplist)、hash表、跳表(skip list)和整数数组」。
redis
数据类型和底层数据结构有如下对应关系:
2.1.字符串类型
底层数据结构是动态字符串。
2.2.列表
如果同时满足下面条件,就使用压缩列表,否则使用双向链表。
-
列表中单个元素小于
64
字节 -
列表中元素个数少于
512
「压缩列表」在内存中是一块儿连续的内存空间,结构如下:
「压缩列表查找时间复杂度是o(n)
」
2.3.集合
如果同时满足下面条件,就使用有序整数数组,否则使用hash表。
-
集合中元素都是整数类型
-
集合中元素个数不超过
512
个
2.4.有序集合
如果同时满足下面2个条件,就使用压缩列表,否则使用跳表。
-
集合中元素都小于
64
字节 -
集合中元素个数小于
128
个
「注意:有序集合还有一个HASH
表用于保存集合中元素的分数,做ZSCORE
操作时,查询的就是这个HASH
表,所以效率很高。」
「跳表」的结构如下:
如果不加索引,查找10
这个数字需要查询10
次,使用了二级索引,查找10
这个数字需要5
次,而使用一级索引,需要查询3
次。
❝跳表的每一层都是一个有序链表,最下面一层保存了全部数据。跳表插入、删除、查询的时间复杂度是
❞o(logN)
。跳表需要存储额外的索引节点,会增加额外的空间开销。
2.5.字典
如果同时满足下面2个条件,就使用压缩列表,否则使用hash表。
-
字典中每个
entry
的key/value
都小于64字节 -
字典中元素个数小于
512
个
3 redis缓存淘汰策略
redis
总共有8
种淘汰策略,如下图:
volatile-lfu
和allkeys-lfu
策略是4.0
版本新增的。
-
「lru」是按照数据的最近最少访问原则来淘汰数据,可能存在的问题是如果大批量冷数据最近被访问了一次,就会占用大量内存空间,如果缓存满了,部分热数据就会被淘汰掉。
-
「lfu」是按照数据的最小访问频率访问次数原则来淘汰数据,如果两个数据的访问次数相同,则把访问时间较早的数据淘汰。
4 redis数据持久化
redis
持久化的方式有2
种,一种是写后日志(AOF)
,一种是内存快照(RDB)
。
4.1.AOF日志
AOF
日志记录了每一条收到的命令,redis
故障宕机恢复时,可以加载AOF
日志中的命令进行重放来进行故障恢复。AOF
有3
种同步策略,如下图:
❝如果不是对丢失数据特别敏感的业务,推荐使用
❞everysec
,对主线程的阻塞少,故障后丢失数据只有1s
。
4.2.RDB快照
RDB
快照是一个内存快照,记录了redis
某一时刻的全部数据。
4.3.混合日志
从redis4.0
开始,AOF
文件也可以保存RDB
快照,AOF
重写的时候redis
会把AOF
文件内容清空,先记录一份RDB
快照,这份数据以"REDIS"
开头。记录RDB
内容后,AOF
文件会接着记录AOF
命令。故障恢复时,先加载AOF
文件中RDB
快照,然后回放AOF
文件中后面的命令。
4.4.主从同步
redis
主从同步时,主节点会先生成一份RDB
快照发送给从节点,把快照之后的命令写入主从同步缓存区(replication buffer)
,从节点把RDB
文件加载完成后,主节点把缓存区命令发送给从节点。
4.5.AOF重写
AOF
日志是用记录命令的方式追加的,这样可能存在对同一个key
的多条命令,这些命令是可以合并成1
条的。比如对同一个key
的多个set
操作日志,可以合成一条。
4.6.阻塞点
AOF
重写和RDB
快照执行的过程中,redis
都会fork
一个子进程来执行操作,子进程执行过程中是不是阻塞主线程的。
「但是要注意2
点:」
-
fork
子进程的过程中,redis
主线程会拷贝一份内存页表(记录了虚拟内存和物理内存的映射关系)给子进程,这个过程是阻塞的,redis
主线程内存越大,阻塞时间越长; -
子进程和
redis
主线程共用一块儿物理内存,如果新的请求到来,必须使用copy on write
的方式,拷贝要修改的数据页到新的内存空间进行修改。如下图:
❝注意:如果开启了内存大页,每次拷贝都需要分配2MB的内存。
❞
5 redis高可用
下图是一个「一主二从三哨兵」的架构图:
从图我们可以看到哨兵之间、哨兵和主从节点之间、哨兵和客户端之间都建立了连接。
如果主节点挂了,哨兵集群需要完成主从切换,如下图:
下面我们依次来聊一下这4个步骤「5.1~5.4」。
5.1.判断主节点下线
当一个哨兵监控到主节点下线时,就会给其他哨兵发送确认命令,其他命令会根据自己的判断回复"Y"
或"N"
。
如果有n/2 + 1
以上数量的哨兵都认为主节点下线了,才会判定主节点下线。这里的n
是哨兵集群的数量。
❝❞
n/2 + 1
这个参数由quorum
参数配置,比如有5
个哨兵,这里一般配置成3
。也可以配置成其他值。
5.2.选举新主节点
主节点被判定下线后,哨兵集群会重新选择新的主节点。
5.2.1 淘汰不稳定从节点
根据配置参数down-after-milliseconds * 10
来淘汰。
「down-after-milliseconds」表示主从节点断开时间,10
表示次数,如果从节点跟主节点断开时间超过down-after-milliseconds
的次数达到了10
次以上,从节点就被淘汰了。
5.2.2 slave-priority参数
「slave-priority」参数配置了从节点的优先级,选择从节点时哨兵会优先选择优先级高的从节点。
5.2.3 复制进度
redis
有一个记录主从增量复制的缓存