Redis面试之常见问题
转载于:https://mp.weixin.qq.com/s/qvXm1pU8T_2mCZCjkTR7QA
1 Redis常见面试问题
1.1 Redis是单线程还是多线程
Redis
不同版本之间采用的线程模型是不一样的,在Redis4.0
版本之前使用的是单线程模型
,在4.0
版本之后增加了多线程的支持。
在4.0
之前虽然说Redis
是单线程,也只是说它的网络I/O
线程以及Set
和 Get
操作是由一个线程完成的。但是Redis
的持久化
、集群同步
还是使用其他线程来完成。
4.0
之后引入了多线程
的支持,主要是体现在大数据的异步删除
功能上,例如 unlink key、flushdb async、flushall async
等
随着底层网络硬件性能的提升,Redis
的性能瓶颈逐渐体现在网络 I/O
的读/写上,单个线程处理网络读/写的速度跟不上底层网络硬件执行的速度。读/写网络
的读/写系统调用占用了 Redis
执行期间大部分 CPU
时间,所以 Redis
采用多个 I/O 线程
来处理网络请求
,提高网络请求处理的并行度
不过Redis
从 6.0
版本开始正式宣布支持多线程模型
,需要注意的是,Redis
多 I/O
线程模型只用来处理网络读/写请求
,Redis
的读/写命令
依然是单线程
处理的。
1.2 使用单线程原因
那为什么Redis在4.0之前会选择使用单线程?而且使用单线程还那么快?
选择单线程主要是使用简单,不存在锁竞争
,可以在无锁的情况下完成所有操作,不存在死锁和线程切换带来的性能和时间上的开销,但同时单线程也不能完全发挥出多核CPU性能
为什么单线程那么快主要有以下几个原因:
Redis
的大部分操作都在内存中完成,内存中的执行效率本身就很快,并且采用了高效的数据结构,比如哈希表和跳表。- 使用单线程避免了多线程的竞争,省去了多线程切换带来的时间和性能开销,并且不会出现死锁。
- 采用
I/O
多路复用机制处理大量客户端的Socket
请求,因为这是基于非阻塞的 I/O 模型,这就让Redis
可以高效地进行网络通信,I/O的读写流程也不再阻塞。 - 很多的客户端连接先到linux中的内核,内核和redis中间使用的是epoll(非阻塞的多路复用),那些进程一笔一笔的进行的。
在分布式情况下,这个数据一致性很重要。每个连接里边命令是顺序到达、顺序处理的,但是如果说里面有个key,这个key为a,那么两个客户端发了一个对a的操作,那么无论从网络当中跳跃谁先到达的,或者指定谁先轮到谁了。那么其实这两个人对一个的操作,很难判定是谁先谁后,但是如果是你一个人,它里边线性,而且没有使用多线程,线程还是安全的,虽然它可以有多线程,但是线程安全,对a的操作,这边能控制住,先创建a再删除a,只要这边能操作的话,那么这个数据是可以保证的。如果是单线程,这个客户端就是一个线程,就是一个socket里面也是一个线程,那么这个线程肯定是先发出一个创建a再发出一个删除a,但是如果客户端里边是多线程,那么这里一个创建命令和删除命令,指不定谁跑到前面了,如果线程不是安全的话,那么有可能先把删除的发出去,再把创建的发出去。
点击了解Linux中epoll原理机制
1.3 Redis高可用
Redis实现高可用主要有三种方式:主从复制
、哨兵模式
,以及 Redis 集群
1.3.1 主从复制
将从前的一台 Redis
服务器,同步数据到多台从 Redis
服务器上,即一主多从的模式,这个跟MySQL
主从复制的原理一样。
点击了解redis 持久化中的主从复制同步
1.3.2 哨兵模式
1.3.2.1 简介
使用 Redis
主从复制的时候,会有一个问题,就是当 Redis
的主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis
增加了哨兵模式(因为哨兵模式做到了可以监控主从服务器,并且提供自动容灾恢复的功能)。
它专注于对 Redis 实例
(主节点、从节点)运行状态的监控,并能够在主节点发生故障时通过一系列的机制实现选主
及主从切换
,实现自动故障转移,确保整个 Redis 系统的可用性。
sentinel
主要做四件事情:
-
监控
master
和slave
状态,判断是否下线。- 每秒一次的频率向
master
和slave
以及其他sentinel
发送PING
命令,如果该节点距离最后一次响应 PING 的时间超过down-after-milliseconds
选项所指定的值(即在设定的时间内没有回复心跳包), 则这个实例会被Sentinel
标记为主观下线,当master
被标记主观下线。 - 其他正在监视这个
master
的所有sentinel
会按照每秒一次的频率确认master
是否主观下线。 - 当超过配置的
quorum
数量的sentinel
都认为master
主观下线,则标记这个master
客观下线。
- 每秒一次的频率向
-
选举新
master
,如果 master 出现故障,sentine 需要选举一个 slave 晋升为新 master。晋升为新 master 的 slave 是有条件的,先过滤不满足条件的,再打分排优先级。- 过滤掉下线、网络异常的
slave
。 - 过滤掉经常与
master
断开的slave
。 slave
优先级,通过replica-priority 100
配置,值越高,优先级越高。- 若优先级相同,则复制偏移量(
processed replication offset
)最大的从节点,已复制的数据量越多越好,slave_repl_offset
与master_repl_offset
差值越小。 slave runID
,在优先级和复制进度都相同的情况下,runID
最小的 slave 得分最高,会被选为新主库。
- 过滤掉下线、网络异常的
-
选举领导者哨兵(
Leader Sentinel
),执行主从切换,从sentinel
集群中选举一个leader
执行故障自动切换。
成为leader
的条件是收到足够的赞成票(大于等于quorum
和(总节点数/2 + 1
)的最大值)。
第一个判定master
主观下线的sentinel
收到其他sentinel
节点的回复并确定master
客观下线后,就会给其他sentinel
节点发送命令申请成为 leader。
选举领导者哨兵而不是直接从从节点中选举新的主节点,主要是为了以下原因:- 协调一致性:通过选举领导者哨兵,可以确保在集群中仅有一个哨兵负责主从切换,避免多个哨兵同时进行切换操作导致的不一致性。
- 集中决策:领导者哨兵集中管理整个主从切换过程,使得切换过程更加有序和可控。
- 分担职责:哨兵负责监控和管理 Redis 节点,而从节点主要用于数据复制和故障切换。在发生故障时,选举领导者哨兵来管理切换过程,可以让从节点专注于数据同步,分担系统负载
-
通知,通知其他
slave
执行replicaof
与新的master
同步数据,并通知客户端与新 master 建立连接。
-
哨兵如何通知客户端?
选出新主节点后,哨兵的任务还没完。它需要告诉所有从节点
和客户端
:主节点已经更换
对于从节点,哨兵会自动修改它们的复制目标,对于客户端,哨兵会通过发布订阅通知它们新的主节点地址。
哨兵会通过Redis
的发布订阅
系统,把新主节点的信息推送给所有订阅的客户端。
例如,使用SENTINEL get-master-addr-by-name mymaster
命令可以获取新主节点的 IP 和端口。
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
# 返回新主节点的 IP 和端口,比如 [ "127.0.0.1", "6380" ]
1.3.2.2 哨兵机制的高可用性保障
一个哨兵单点可能会挂掉,所以 Redis
支持多哨兵
协同工作。这种模式下,哨兵之间也会相互通信,通过 Raft
类似的选举机制选出一个领头哨兵
(Leader Sentinel
),由它负责协调故障转移。
每个哨兵实例都可以彼此通信,确保在任何一个哨兵挂掉的情况下,系统仍然能正常工作。
哨兵机制是 Redis
提供的高可用
解决方案,主要功能包括监控主从节点状态、进行故障转移以及通知客户端更新主节点信息。
监控
:哨兵通过PING
命令定期检测主从节点是否存活,基于响应时间判断节点状态。选主
:哨兵根据复制偏移量、优先级和网络延迟等标准选举新的主节点,保证数据一致性和快速恢复。高可用性
:通过多个哨兵协同工作,避免单点故障。通知
:哨兵利用发布订阅机制通知客户端主节点变更,确保业务无缝切换。
1.3.3 Redis Cluster(集群)
哨兵模式
基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。因此,Reids Cluster
集群(切片集群的实现方案)应运而生,它在Redis3.0加入的,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它可以保存大量数据,即分散数据到各个Redis实例,还提供复制和故障转移的功能。
Redis Cluster
是一种分布式去中心化的运行模式,是在 Redis 3.0 版本中推出的 Redis 集群方案,通过分片(sharding
)来进行数据管理(分治思想的一种实践),并提供复制和故障转移功能,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高 Redis
服务的读写性能。
使用哨兵模式在数据上有副本数据做保证,在可用性上又有哨兵监控,一旦master
宕机会选举salve
节点为master
节点,那为什么还需要使用集群模式呢?
哨兵模式归根节点还是主从模式
,在主从模式下我们可以通过增加salve
节点来扩展读并发能力,但是没办法扩展写能力和存储能力,存储能力只能是master
节点能够承载的上限。所以为了扩展写能力和存储能力,我们就需要引入集群模式。
集群中那么多Master
节点,redis cluster
在存储的时候如何确定选择哪个节点呢?
Redis Cluster
采用的是数据分片
实现节点选择的
1.4 Redis内存(数据)淘汰策略
在redis
中,我们是可以去设置最大使用内存大小server.maxmemory
的,当redis
内存数据集大小上升到一定程度的时候,就会施行数据淘汰机制。
不同位数的操作系统,maxmemory
的默认值是不同的:
- 在 64 位操作系统中,
maxmemory
的默认值是 0,表示没有内存大小限制,那么不管用户存放多少数据到Redis
中,Redis
也不会对可用内存进行检查,直到 Redis 实例因内存不足而崩溃也无作为。 - 在 32 位操作系统中,
maxmemory
的默认值是3G
,因为 32 位的机器最大只支持4GB
的内存,而系统本身就需要一定的内存资源来支持运行,所以 32 位操作系统限制最大 3 GB 的可用内存是非常合理的,这样可以避免因为内存不足而导致Redis
实例崩溃。
Redis
提供了8种数据淘汰策略,分为不进行数据淘汰
和进行数据淘汰
两类策略,
不进行数据淘汰的策略 noeviction
(Redis3.0
之后,默认的内存淘汰策略),它表示当运行内存超过最大设置内存时,不淘汰任何数据,这时如果有新的数据写入,会报错通知禁止写入,不淘汰任何数据,但是如果没用数据写入的话,只是单纯的查询或者删除操作的话,还是可以正常工作。
no-enviction
:禁止淘汰数据,如果redis写满了将不提供写请求,直接返回错误
进行数据淘汰的策略:
volatile-lru
:从已经设置过期时间的数据集中,挑选最近最少使用的数据淘汰,即:最久未使用的键值volatile-ttl
:从已经设置过期时间的数据集中,挑选即将要过期的数据淘汰。volatile-random
:从已经设置过期时间的数据集中,随机挑选数据淘汰。volatile-lfu
:从已经设置过期时间的数据集中,会使用LFU
算法选择设置了过期时间的键值对,即:最不常用的键值allkeys-lru
:从所有的数据集中,挑选最近最少使用的数据淘汰。allkeys-random
:从所有的数据集中,随机挑选数据淘汰。allkeys-lfu
:淘汰整个键值中最不常用的键值
附录:LRU
和LFU
是不同的:
LRU
是最近最少使用页面置换算法(Least Recently Used
),也就是首先淘汰最长时间未被使用的页面LFU
是最近最不常用页面置换算法(Least Frequently Used
),也就是淘汰一定时期内被访问次数最少的页
使用策略规则:
- 如果数据呈现
幂律分布
,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
- 如果数据呈现
平等分布
,也就是所有的数据访问频率都相同,则使用allkeys-random
1.5 Redis过期键删除策略
Redis
过期键删除策略:
定时删除
:在设置键的过期时间的同时,创建一个timer
,让定时器在键的过期时间到达时,立即执行对键的删除操作。(主动删除)
对内存友好,但是对cpu时间不友好,有较多过期键的而情况下,删除过期键会占用相当一部分cpu时间。惰性删除
:放任过期键不管,但是每次从键空间中获取键时,都检查取到的键是否过去,如果过期就删除,如果没过期就返回该键。(被动删除)
对cpu
时间友好,程序只会在取出键的时候才会对键进行过期检查,这不会在删除其他无关过期键上花费任何cpu时间,但是如果一个键已经过期,而这个键又保留在数据库中,那么只要这个过期键不被删除,他所占用的内存就不会释放,对内存不友好。
这种方式在Redis中主要是通过expireIfNeeded
函数来实现的
在Redis 4.0
之后引入的,叫做lazyfree_lazy_expire
,它决定了是否异步删除过期键
。如果设置为1
,表示使用异步删除,避免阻塞其他操作;如果设置为0
,则会同步删除,删除操作会阻塞当前请求,直到删除完成定期删除
:每隔一段时间就对数据库进行一次检查,删除里面的过期键。(主动删除)采用对内存
和cpu
时间折中的方法,每隔一段时间就对一些key
进行采样检查,检查是否过期,如果过期就进行删除
定期删除的检查周期默认是每秒执行10次
,可以通过hz
配置来调整。默认情况下,hz值为10
。
定期删除的逻辑是这样的:- 采样一定个数的
key
(默认20
),采样的个数可以进行配置,并将其中过期的key
全部删除; - 如果过期
key
的占比超过可接受的过期key
的百分比(默认25%
),则重复删除的过程,直到过期key
的比例降至可接受的过期key
的百分比以下 注意
:为了防止定期删除过程中的循环过长
,Redis
限制了每轮检查的最大时间,默认情况下不会超过25毫秒
。如果在25毫秒
内没有完成过期键的删除,Redis
会强制退出当前轮次,等到下一轮继续处理。
- 采样一定个数的
1.6 Redis的key和value可以存储的最大值分别是多少
虽然Key
的大小上限为512M
,但是一般建议key
的大小不要超过1KB
,这样既可以节约存储空间,又有利于Redis
进行检索。
value
的最大值也是512M
。对于String
类型的value
值上限为512M
,而集合、链表、哈希等key
类型,单个元素的value
上限也为512M
1.7 Redis实现数据的去重
Redis
的set
:它可以去除重复元素,也可以快速判断某一个元素是否存在于集合中,如果元素很多(比如上亿的计数),占用内存很大。Redis
的bit
:它可以用来实现比set内存高度压缩的计数,它通过一个bit
设置为1
或者0
,表示存储某个元素是否存在信息。例如网站唯一访客计数,可以把user_id
作为bit
的偏移量offset
,如设置为1
表示有访问,使用1 MB
的空间就可以存放800多万用户的一天访问计数情况。HyperLogLog
:实现超大数据量精确的唯一计数都是比较困难的,HyperLogLog
可以仅仅使用 12 k左右的内存,实现上亿的唯一计数,而且误差控制在百分之一左右。bloomfilter
布隆过滤器:布隆过滤器是一种占用空间很小的数据结构,它由一个很长的二进制向量和一组Hash映射函数组成,它用于检索一个元素是否在一个集合中
1.8 Redis序列化
Redis
什么时候需要序列化?
序列化
:将Java
对象转换成字节流的过程。反序列化
:将字节流转换成Java
对象的过程。
为什么需要序列化呢?
打个比喻:作为大城市漂泊的码农,搬家是常态。当我们搬书桌时,桌子太大了就通不过比较小的门,因此我们需要把它拆开再搬过去,这个拆桌子的过程就是序列化。而我们把书桌复原回来(安装)的过程就是反序列化啦。
比如想把内存中的对象状态保存到一个文件中或者数据库中的时候(最常用,如保存到redis
);再比喻想用套接字在网络上传送对象的时候,都需要序列化。
RedisSerializer
接口 是 Redis
序列化接口,用于 Redis KEY
和 VALUE
的序列化,有如下序列化方式:
JDK
序列化方式 (默认)String
序列化方式JSON
序列化方式XML
序列化方式
1.9 大key
1.9.1 定义
Redis
中的 大key
是指存储在Redis
中的占用内存较大的键值对。大key
可能会导致Redis
的性能下降,因为大key
占用的内存较多,需要较长的时间来进行读写操作。而且,当大key
被删除时,会阻塞Redis
的其他操作。
常见的大key包括:
- 存储大量数据的字符串类型键值对。
- 存储大量元素的列表、集合或有序集合。
- 包含大量字段的哈希表。
1.9.2 大Key解决方案
为了避免大key
对Redis
性能的影响,可以采取以下措施:
- 将
大key
拆分为多个较小的键值对,以减少每个键值对的内存占用。 - 使用分布式缓存,将
大key
分散到多个Redis
实例上。 - 使用压缩算法对
大key
进行压缩,减少内存占用。 - 使用
Redis
的分片功能,将大key
分散到多个分片上,减少单个Redis
实例的负载。 - 对
大Key
进行清理。将不适用Redis能力的数据存至其它存储,并在Redis中删除此类数据。注意,要使用异步删除。 - 监控Redis的内存水位。可以通过监控系统设置合理的Redis内存报警阈值进行提醒,例如Redis内存使用率超过70%、Redis的内存在1小时内增长率超过20%等。
- 对过期数据进行定期清。堆积大量过期数据会造成
大Ke
y的产生,例如在HASH数据类型中以增量的形式不断写入大量数据而忽略了数据的时效性。可以通过定时任务的方式对失效数据进行清理。
1.10 热Key
1.10.1 定义
Redis热key
是指在Redis
中被频繁访问的键。当某个键被频繁访问时,它就被认为是热key
。热key
通常是由于某些热门操作、热门数据或者高并发访问所导致的
通常以其接收到的 Key
被请求频率来判定,例如:
QPS
集中在特定的Key:Redis实例的总QPS(每秒查询率)为10,000,而其中一个Key的每秒访问量达到了7,000。- 带宽使用率集中在特定的Key:对一个拥有上千个成员且总大小为1 MB的HASH Key每秒发送大量的HGETALL操作请求。
- CPU使用时间占比集中在特定的Key:对一个拥有数万个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求。
1.10.2 如何解决热key
热key
可能对Redis
的性能产生重大影响。当一个键被频繁访问时,Redis
需要频繁地从内存中读取或写入该键的值,这可能导致内存带宽的瓶颈、CPU利用率的增加以及延迟的增加。此外,如果一个热key的值过大,可能会占用大量的内存,进一步影响Redis的性能
解决方案:
- 在
Redis
集群架构中对热Key
进行复制,数据分片
在Redis
集群架构中,由于热Key
的迁移粒度问题,无法将请求分散至其他数据分片,导致单个数据分片的压力无法下降。此时,可以将对应热Key进行复制并迁移至其他数据分片,例如将热Key foo复制出3个内容完全一样的Key并名为foo2、foo3、foo4,将这三个Key迁移到其他数据分片来解决单个数据分片的热Key压力。 - 使用读写分离架构。
如果热Key
的产生来自于读请求,您可以将实例改造成读写分离架构来降低每个数据分片的读请求压力,甚至可以不断地增加从节点。但是读写分离架构在增加业务代码复杂度的同时,也会增加Redis集群架构复杂度。不仅要为多个从节点提供转发层(如Proxy,LVS等)来实现负载均衡,还要考虑从节点数量显著增加后带来故障率增加的问题。Redis集群架构变更会为监控、运维、故障处理带来了更大的挑战。 - 使用合适的数据结构
根据实际情况选择合适的数据结构,如列表、集合、有序集合等,来存储热key的值,以提高读写性能。 - 缓存策略
使用合理的缓存策略,比如设置合适的过期时间、使用LRU算法等,以减少对热key的访问频率。 - 持久化策略
根据业务需求选择合适的持久化方式,如RDB快照、AOF日志等,以确保数据的安全性和可靠性。
1.11 Redis中缓冲区
Redis中的缓冲区主要根据其功能和用途进行划分,可以归纳为以下几种:
- 客户端缓冲区:
- 输入缓冲区:缓存客户端发送过来的命令,
Redis
主线程从该缓冲区中读取命令进行处理。 - 输出缓冲区:当
Redis
主线程处理完数据后,将结果写入该缓冲区,再返回给客户端。 - 缓存区溢出原因:
- 写入了BigKey,即一次性写入了大量数据,超过了缓冲区的大小。
- 服务端处理请求的速度过慢,导致无法及时处理请求,使得客户端发送的请求在缓冲区内越积越多。
- 输入缓冲区:缓存客户端发送过来的命令,
- 复制缓冲区:
- 复制缓冲区:在全量复制过程中,主节点在向从节点传输
RDB
文件的同时,会继续接收客户端发送的写命令请求。这些写命令会先保存在复制缓冲区中,等RDB
文件传输完成后,再发送给从节点去执行。 - 复制积压缓冲区:在增量复制时,主节点和从节点进行常规同步时,会把写命令暂存在
复制积压缓冲区
中。 - 溢出原因:
- 主库传输RDB文件以及从库加载RDB文件耗时长,同时主库接收的写命令操作较多。
- 缓冲区大小设置不合理。
- 复制缓冲区:在全量复制过程中,主节点在向从节点传输
- AOF缓冲区:
- AOF缓冲区:当
Redis
进行持久化时,会先将客户端传来的命令存放在AOF
缓冲区,再根据具体的策略(always、everysec、no)去写入磁盘中的AOF文件中。 - AOF重写缓冲区:在
Redis
进行AOF
重写时,主进程会fork一个子进程进行AOF
重写,此时主进程接收的指令会存放在AOF重写缓冲区中。当AOF重写完成后,这些指令会被追加到AOF文件中。
- AOF缓冲区:当
- 内存级缓存:
虽然不是严格意义上的缓冲区
,但Redis
的内存级缓存是其最常见的使用场景。通过将数据存储在内存中,减少读取数据库的频率,提高数据访问速度。 - 其他内存使用:
Redis
空进程自身内存消耗非常少,通常used_memory_rss
在3MB左右,used_memory在800KB左右。- 对象内存:存储着用户所有的数据,消耗可以简单理解为sizeof(keys)+sizeof(values)。
- 内存碎片:正常的碎片率在1.03左右
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了