Redis 常见面试题汇总

1,251 阅读12分钟

1. 写在前面

小伙伴们有时候比较排斥这些八股,但我觉得八股就好比数学课本中的公式和课后习题,关键你要发散开,而不是搞题海战术

2. 面试题

2.1 redis变慢的原因

存储了bigkey,淘汰删除bigkey释放内存的时候会耗时比较久

redis 实例设置了内存上限maxmemory,当内存达到maxmemory,每次写入数据都会从实例剔除一部分数据,让整个实例的内存维持在maxmemory以下,然后才能把新数据写进来

开启了内存大页,当redis执行后台RDB或者AOF rewrite的时候,采用fork子进程的方式来处理,但主进程依旧可以接受处理写请求,进来的写请求采用cow写时复制的方式操作内存数据,这样的好处是父进程有任何写操作,并不会影响子进程的数据持久化,那么在此期间,客户端即便只修改10b的数据,redis在申请内存时会以2MB为单位向系统申请内存,申请内存耗时变长,进而导致每个写请求的延迟增加,影响redis性能

使用了swap, 操作系统为了缓解内存不足对应的程序影响,允许把一部分内存中的数据交换到磁盘中,已达到应用程序对内存使用的缓冲,这些内存数据被换到磁盘上的区域就是swap 当内存中的数据换到磁盘上后,redis再访问这些数据时就需要从磁盘读取,访问磁盘速度要比访问内存慢几百遍,解决方案就死增加机器的内存,让redis有足够的内存使用

网络带宽过载,服务器TCP层和网络层就会出现发送延迟,丢包等情况,redis的高性能除了操作内存就是网络IO课,如果网络IO存在瓶颈,也会影响redis的性能 解决方案:及时确认占满网络带宽的是不是属于正常的业务访问,是的话及时扩容或者迁移实例

频繁的短连接,频繁的短连接会导致redis大量时间耗在链接的建立和释放上,TCP的三次握手和四次挥手也会增加访问延迟,应该使用长连接操作redis,避免频繁的短连接

2.2 什么是redis的大key以及怎么处理

value是string,超过5MB

value 是zset、hash、list、set等集合类型的,成员数量超过1w个,也不是绝对的,根据value的成员数量和大小来确定

当value是string,可以使用序列化,压缩算法控制value的大小,但是 也会带来时间的消耗

拆分key,使用meultiget来获取

2.2 缓存击穿和穿透

击穿:大量的请求同时查询一个key的时候,这个key突然失效,导致大量的请求落到DB,击穿就是查询失效的key,穿透是查询不存在的key

如何解决击穿:

加分布式锁,并发的多个请求只有第一个请求线程拿到锁并执行数据库查询,其他线程拿不到就阻塞,第一个线程写入缓存,后续直接走缓存

热点数据不过期,设置为不过期,然后由定时任务去异步加载数据,更新缓存,要考虑业务能承受的数据不一致的时间

如何解决穿透

缓存空值,不去查数据库

采用bloom,将所有的数据hash到一个足够大的bitmap,查询不存在的数据会被bitmap拦截掉,避免DB的查询压力,可能存在,一定不存在

2.3 缓存雪崩

设置缓存的时候采用了相同的过期时间,导致缓存某一时刻同时失效,请求全部到DB,导致DB over

DB 查询加锁排队查询

设置二级缓存

2.4 sortedSet 和list的比较

相同点:有序,都可以获取某个范围的元素

不同点: 列表基于链表实现,获取两端元素快,访问中间元素速度慢,有序集合基于散列表和跳跃表,访问中间元素时间复杂度OlogN,列表不能简单的调整某个元素的位置,有序列表可以,有序集合更耗内存

2.5 redis的keys命令的问题

redis命令的执行是单线程的,keys命令会导致线程阻塞一段时间,直到执行完毕服务才能恢复

如何解决:用scan,采用渐进式遍历的方式来解决keys命令带来的阻塞问题,每次scan命令的时间复杂度是O1,但是要真正实现keys的功能,需要执行多次的scan

scan的缺点:scan的过程有crud,导致新增的key没有遍历到,遍历出现了重复的key等情况

2.6 解释一下redis数据倾斜的问题

image.png

热点数据多副本方法只能针对只读的热点数据。如果热点数据是有读有写的话,就不适合采用多副本方法了,因为要保证多副本间的数据一致性,会带来额外的开销。

对于有读有写的热点数据,我们就要给实例本身增加资源了,例如使用配置更高的机器,来应对大量的访问压力。

2.7 redis 有哪些集群

Redis 集群有几种实现方式,一个是基于主从复制的哨兵集群、一个是 切片集群 Redis Cluster。

基于主从复制的哨兵集群

就是在 Redis集中包括一个 Master 节点和多个 Slave 节点。 Master 负责数据的读写,Slave 节点负责数据的读取。 Master 上收到的数据变更,会同步到 Slave 节点上实现数据的同步。 通过这种架构实现可以 Redis 的读写分离,提升数据的查询性能。

Redis 主从集群不提供容错和恢复功能,一旦 Master 节点挂了,不会自动选出新的 Master,导致后续客户端所有写请求直接失败。 所以Redis提供了哨兵机制,专门用来监听Redis主从集群提供故障的自动处理能力, 哨兵会监控 Redis 主从节点的状态,当 Master 节点出现故障,会自动从剩余的 Slave 节点中选一个新的 Master。

哨兵模式下虽然解决了 Master 选举的问题,但是在线扩容的问题还是没有解决

切片集群 Redis Cluster

它实现了Redis的分布式存储, 也就是每个节点存储不同的数据实现数据的分片。 在 Redis Cluster 中,引入了 Slot 槽来实现数据分片,Slot 的整体取值范围是 0~16383, 每个节点会分配一个 Slot 区间 当我们存取 Key 的时候,Redis 根据 key 计算得到一个 Slot 的值,然后找到对应的节点进行数据的读写。

切片集群不可避免地涉及到多个实例的分布式管理问题, 客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端, Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,Redis Cluster的数据迁移是同步的,迁移一个key会同时阻塞源节点和目标节点,迁移过程中会有性能问题。 Redis Cluster在每个节点记录完整的映射关系(便于纠正客户端的错误路由请求)

优缺点和区别

  1. Redis 哨兵集群是基于主从复制来实现的,所以它可以实现读写分离,分担 Redis 读操作的压力 而 Redis Cluster 集群的 Slave 节点只是实现冷备机制,它只有在 Master 宕机之后才 会工作。
  1. Redis 哨兵集群无法在线扩容,所以它的并发压力受限于单个服务器的资源配置。 Redis Cluster 提供了基于 Slot 槽的数据分片机制,可以实现在线扩容提升写数据的性 能
  1. 从集群架构上来说,Redis 哨兵集群是一主多从, 而 Redis Cluster 是多主多从

2.7 redis脑裂 的解释

master所在的网络产生了分区,和所有的slave失联了,但是连接master的长连接客户端并不知道,照样向master写入数据,这些数据是无法同步给slave的,这个时候哨兵也发现master失联了,认为master挂了,会从剩下的slave挑选一个作为master

redis脑裂,会产生数据丢失的问题, 老的master恢复网络以后,会清空自己的数据,然后复制新的master的数据,故障期间写入老的master的数据就全都丢失了

通过两个配置参数来解决 min-slaves-to-write:主库能进行数据同步的最小从库数量,老的master失联了,这里设置为1表示至少得有一个slave,如果一个slave都没有同步,就会拒绝写入,直接拒绝错误给客户端

min-slaves-max-lag:主从库间进行数据复制的时候,从库向主库发送ack消息的最大延迟,如果没有收到ack,就会拒绝写入

可以参考这个: www.bilibili.com/video/BV1oL…

2.8 什么是渐进式rehash

Redis hash的数据结构默认使用了两个全局哈希表,一个用于当前使用,称 为主哈希表,一个用于扩容,称为备用哈希表

有一个字段rehashidx用来控制rehash,默认是-1,将备用hash表初始化为2倍的数组,rehashidx为0,此时hash表进入rehash状态,之后对hash表的 crud,都会进行一次单步的rehash操作,迁移完一个索引以后,rehashidx 加1,rehash结束后,把主hash表的指针指向备用hash表,rehashidx置为-1

具体我参考的这个大佬的:www.bilibili.com/video/BV1uV…

2.9 为什么 Redis 集群的最大槽数是 16384 个

Redis-Cluster 集群模式使用了哈希槽(hash slot)来实现数据的分片,hash slot 的 默认长度是 16384. 对于网络通信开销的平衡:Redis 集群中每个节点会发送心跳消息,而心跳包中会携带节点的完整配置,

如果采用 16384 个插槽,而每个插槽信息占用的位数为 1,则每个节点需要维护的配置信息占用空间大小为 2KB, CRC16 算法产生的 hash 值有 16 位,如果按照 2^16 次方计算得到 65536 个 槽, 那就会导致每个节点维护的配置信息占 8kb。8kb 数量的心跳数据看起来不大,但是这个心跳包每秒都需要把当前节点的信息同步给 集群中的其他节点

2.10 Redis到底是单线程还是多线程

并不是全面单线程

redis对外提供键值对操作的主要流程是单线程,也就是网络IO和数据读写是单个线程 但是持久化,异步删除、集群数据同步是额外的异步线程来完成的

效率高的原因:

纯内存操作

降低了CPU消耗,不需要CPU来回切换空间

采用了多路复用的同步非阻塞网络模型

2.11 Redis 有序集合 ZSet 的数据结构

是由 ziplist (压缩列表) 或 skiplist (跳跃表) 组成的。

压缩列表 ziplist 本质上就是一个字节数组,是 Redis 为了节约内存而设计的一种线性数据结构,可以包含多个元素,每个元素可以是一个字节数组或一个整数。

跳跃表 skiplist 是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表支持平均 O(logN)、最坏 O(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点。

跳跃表 Skip List,也称之为跳表,是一种数据结构,用于在有序元素的集合中进行高效的查找操作。它通过添加多层链表的方式,提供了一种以空间换时间的方式来加速查找。跳跃表由一个带有多层节点的链表组成,每一层都是原始链表的一个子集。最底层是一个完整的有序链表,包含所有元素。每个更高层级都是下层级的子集,通过添加额外的指针来跳过一些元素。这些额外的指针称为“跳跃指针”,它们允许快速访问更远的节点,从而减少了查找所需的比较次数。跳跃表的平均查找时间复杂度为 O(log n),其中 n 是元素的数量。这使得它比普通的有序链表具有更快的查找性能,并且与平衡二叉搜索树(如红黑树)相比,实现起来更为简单。

所谓的随机层数指的是每次添加节点之前,会先生成当前节点的随机层数,根据生成的随机层数来决定将当前节点存在几层链表中

随机层数有 50% 的概率被分配到 Level 1,25% 的概率被分配到 Level 2,12.5% 的概率被分配到 Level 3,以此类推

Redis 跳跃表默认允许最大的层数是 32,此值在 ZSKIPLIST_MAXLEVEL 源码中被定义。

跳跃表是由多个有序的链表组成的,最底层存储了所有元素的数据,这样存储让它的查询效率更高,查询复杂度从 O(n) 变为了 O(log n)。跳跃表的添加流程是根据节点生成的随机层数,将它插入到最底层节点和上层的 N-1 层节点中,描述添加流程的关键就是理解随机层数以及其背后的原理。#

可以参考这个www.javacn.site/company/red…

3. 补充

这个总结的蛮好的 juejin.cn/post/700201…