前言

本篇是整理自己遇到或看到关于redis相关的面试题,答案仅供参考。


`提示:写博客是自己对知识梳理,目前是写给自己看,算是自己学习后的作业,也是为了养成一个良好的习惯。

一、基础篇

redis 为什么那么快?
  1. Redis是单线程模型,没有上下文切换的开销;
  2. 简单的数据类型,时间复杂度都是O(1);
  3. 数据都在内存中,计算快;
  4. 采用了多路复用机制使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率。

为什么说redis是单线程?
	Redis是单线程主要是指 Redis的网络IO和键值对读写是由一个线程来完成的,
这也是Redis对外提供键值存储服务的主要流程。

既然redis是单线程为什么redis 6.0要改成多线程?
	这个官网有原话,大概就是因为redis所有的操作都在内存中,则CPU不是redis瓶颈,随着
科技发展内存也不再那么昂贵,那么redis主要性能问题就是在单线程的IO同步阻塞问题,则在6.0时
对于持久化、异步删除、集群数据同步等同步阻塞功能支持了多线程处理,解决同步阻塞问题。

什么是IO多路复用?redis是那种reactor的网络模型?
	IO多路复用就是通过一种机制,一个线程可以监听多个描述符(socket),
一旦某个描述符就绪,就通知程序(handler)进行相应的读写操作。
	redis是单reactor多线程的网络模型,具体请看下图:

redis 调表时间复杂度 redis时间复杂度理解_redis 调表时间复杂度

二、缓存问题篇

1.经典缓存问题

缓存穿透问题
	场景:缓存中没有数据,DB中也没有数据,请求直接打在DB上。
	方案:布隆过滤器;加强接口的校验,过滤掉无效的请求;
		闪电缓存,设置短时间的过期key,key为请求唯一标识,值为null。
		
缓存击穿问题
	场景:一般是热key刚好过期时,同时有大量请求直接打在了DB上。
	方案:热key数据不设置过期时间;在数据层做互斥锁。
	
缓存雪崩问题
	场景:缓存大面积失效,导致大量的请求直接打在DB上;
	方案:缓存集中过期:将过期时间用随机数打散;服务宕机:采用高可用集群模式;服务做熔断降级保护

缓存污染问题
	场景:热key和其他key没有分开,在高并发时会存在新的缓存将热key缓存淘汰掉,从而导致的缓存污染
	方案:热key分开存放,保证缓存不被污染;淘汰策略由LRU关注于访问时间改为LFU关注于访问频率。

2.缓存一致性问题

缓存和DB一致性问题
	场景:保证缓存和DB数据一致性问题
	方案:最终一致性--延迟双删,保证最多在延迟时间内数据不一致,具体的看下图的分析;
		 强一致性--读写串行,这种虽然保证了强一致性但是违背了使用缓存的初衷,不推荐。
	但是在实际开发中用得最多的还是懒加载模式。

redis 调表时间复杂度 redis时间复杂度理解_数据_02

三、性能优化篇

关于性能优化的一些问题我的上一篇已经有详细从 网络IO、内存利用率、单线程的同步阻塞等
多个维度思考、分析并给出了相关的方案,具体请参考我之前写 redis优化的思考。
	相关问题有以下:
	1. redis性能瓶颈在那?如何优化?
	2. bigkey问题,如何探测bigkey,有哪些方案?
	3. redis如何节约内存?

可以看一下以后这张图加强你对redis相关问题的思维导向。

redis优化的思考

redis 调表时间复杂度 redis时间复杂度理解_缓存_03

四、集群篇

谈谈常见redis集群有哪些?各自解决问题?
	主从模式,关注于数据备份容灾问题,保证数据的高可靠,异常时需要人工接入;
	哨兵模式,关注的是服务的高可用,即异常时自动切换主从;
	切片模式(cluster),支持高可用,数据备份,关注的是高性能,数据分片存储,支持横向扩展。

1.哨兵集群

说一说哨兵集群和cluster集群的组成和作用?
	组成:n*zk(奇数)+ master redis+ n*slave redis 
	作用:多个zk(奇数 zk的一致性算法ZAB)组成的哨兵是支持对redis集群的监控和选举,
	可以实现自动对故障转移,1主多从是为了实现数据备份和异常时主从切换。
	ps:这里就不详细说明了,会抽时间专门的写一篇关于redis集群的博客。
	
谈一谈哨兵集群中,哨兵的作用?
	1. 监控:Sentinel不断的检查主服务和从服务器是否按照预期正常工作;
	2. 提醒:被监控的Redis出现问题时,Sentinel会通知管理员或其他应用程序;
	3. 自动故障转移:监控的主Redis不能正常工作,Sentinel会开始进行故障迁移操作。
	
哨兵集群中,突然主节点宕机了会发生什么?
	1. 删除列表中所有处于下线或者短线状态的Slave;
	2. 删除列表中所有最近5s内没有回复过领头Sentinel的INFO命令的Slave;
	3. 删除所有与下线Master连接断开超过down-after-milliseconds * 10毫秒的Slave;
	4. 领头Sentinel将根据Slave优先级,对列表中剩余的Slave进行排序,并选出其中优先级最高的Slave。
	如果有多个具有相同优先级的Slave,那么领头Sentinel将按照Slave复制偏移量,选出其中偏移量最大的Slave。
	如果有多个优先级最高,偏移量最大的Slave,那么根据运行ID最小原则选出新的Master。

2.Cluster集群

说一说cluster集群的组成和作用?
	组成:Master/Slave +Master/Slave+Master/Slave
	目的:数据分片存储,解决是大数据内存瓶颈的问题
	ps:这里就不详细说明了,会抽时间专门的写一篇关于redis集群的博客。

既然Cluster集群是支持横向扩展,那么它能否无限扩展?如果不能最大是多少?为什么?
	最多支持16384个节点,crc16 算法将数据分布到不同节点上,具体的算法是
crc16(key)mod 16384,则计算出的key都会对应一个编号在 0-16383 之间的哈希槽,
即Cluster集群最大支持16384个节点
	
redis Cluster集群中,突然宕机了一台会发生什么?
	1. 如果半数以上 Master 处于关闭状态那么整个集群处于不可用状态。
	2. 关闭任意一对主从节点会导致部分(大约为整个集群的1/3)失败。
	3. 关闭任意一主,会导致部分写操作失败,是由于从节点不能执行写操作,
		在Slave升级为Master期间会有少量的失败。
	4. 关闭从节点对于整个集群没有影响。

3.主从复制

redis主从复制过程?
	1、slave向master发起同步请求,slave会告知master自己的offset,如果是第一次,offset=-1表示需要全量同步;
	2、slave连上了master,master会给slave分配一个buffer(复制缓冲区);
	3、master fork子进程dump RDB文件,dump完成后告诉父进程,父进程把RDB发给slave(通过slave的socket发过去);
	4、slave载入rdb到本地磁盘;
	5、在master dump RDB期间,master所有写命令,都写到这个buffer然后同步给slave
	6、slave执行同步过来的buffer命令。

redis是如何解决一主多从同步会有什么问题?怎么解决?
	从主从复制过程可以看出,每有一个slave来请求同步数据master都会分配复制缓冲区,通过socket
将数据都同步给slave,则slave越多master同步的开销越大。
	方案:1. 控制从节点的数量;2.启用级联模式进行主从同步。
	
什么是脑裂?脑裂问题如何处理?
	在哨兵集群中,master节点由于网络问题同zk集群断掉了,则zk集群(哨兵)认为master宕机了会在
slave节点中选举一个新master,则当前哨兵集群中有2个master,而之前客户端依旧会往原master写
数据,当原master同zk集群网络恢复后会先删掉自己的数据然后去全量同步新master数据,这就会导致数据
丢失,以上整个过程就是脑裂问题。
	先来看一下关于脑裂网上常见的处理方案:
		min-slaves-to-write:1     表示至少要有一个slave的master才能处理数据
		min-slaves-max-lag:10	  表示数据的复制和同步时间不能超过10s
	1. min-slaves-to-write:1 是可以保证当master和zk集群网络异常时,即便出现脑裂了,
原master也不会处理任何请求,保证数据一致性。
	2. min-slaves-max-lag:10 可以保证只要master宕机了,最多只丢10s数据。
	以上方案看起来可以,但是都是基于保证一致性牺牲可有用性的维度来思考的,但是我个人认为在实际开
发中应该通过业务要求来取舍一致性和可用性,如果业务一致性要求很高的话上面方案是ok,但是redis是
更多作为缓存中间件在实际开发中我们使用(有DB兜底),它更多的需要保证是可用性。
	基于上面分析我更倾向于尽可能的减少出现脑裂情况,即从运维出发将哨兵集群和redis集群部署在同一个局域网中,
尽可能减少哨兵和redis的网络问题。

主从复制过程图:

redis 调表时间复杂度 redis时间复杂度理解_redis_04


主从级联模式图:

redis 调表时间复杂度 redis时间复杂度理解_缓存_05

五、持久化篇

redis支持哪些持久化?
	RDB快照、AOF日志、混合持久化(RDB+增量AOF)redis4.0支持。
	
RDB原理和过程?
	说简单点就是把内存数据持久化到磁盘,使用的是操作系统的多进程COW(写时即复制)机制来实现快照持久化,
具体过程就是 父进程调用fork子进程去遍历内存数据,将内存数据二进制序列化到磁盘上,子进程完成后通知父进程。

RDB和AOF区别?
 RDB:
 	 1.备份方式:RDB是一次全量备份;
     2.数据存储:RDB是内存数据二进制序列化形式,在存储上非常紧凑,即体积较小;
     3.数据恢复:由于体积小,则恢复数据非常快;
     4.启动:redis默认方式。
 AOF:
	 1. 备份方式:AOF连续的增量备份;
	 2. 数据存储:AOF日志存储的是redis服务器顺序指令序列,它在长期的运行中会变的无比庞大;
	 3. 数据恢复:由于AOF体积非常大重放整个AOF非常耗时;
	 4. 启动:手动设置。
  
什么是重写AOF?为什么要重写AOF?它的原理?
	AOF日志会随着实例一直运行,体积会越来越庞大,为了解决这问题,redis支持了重写AOF功能来给
AOF瘦身。
	原理:开辟有个子进程对内存进行遍历,转化成一系列redis的操作指令,序列化到新的AOF日志文件中,
再将操作期间的增量AOF日志追加到新的AOF上,最后用新的AOF替代旧的AOF,从而大大减小了AOF的体积。

redis实例同时开启了AOF和RDB,重启后数据恢复是使用RDB还是AOF?为什么?
	会使用AOF,因为AOF的数据更完整,它默认配置可以保证最多丢失1s数据。

混合持久化作用?它的原理?
	重启redis时,如果用RDB恢复数据虽然速度快但是会丢失大量的数据,采用AOF重放可以保证数据完整性但是超级慢,为了解决这个问题4.0推出了混合持久化。
	原理:AOF在重写时,不再是单纯将内存数据转换为指令命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,
并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,即混合持久化 =RDB+增量AOF。

fork期间会阻塞父进程吗?为什么会阻塞?如何优化?
	fork完成之前,会阻塞父进程,主要是父进程需要拷贝进程中的内存页表给子进程,每个进程都要有自己
的内存页表,所以这个父子进程无法共享,必须要拷贝一份,进程占用的内存越大,拷贝时间越久。
	优化:redis4.0可以开启appendfsync everysec配置,将持久化放到后台线程中去执行,
尽量降低 Redis写磁盘对性能的影响。

RDB过程图:

redis 调表时间复杂度 redis时间复杂度理解_数据_06

AOF重写过程图:

redis 调表时间复杂度 redis时间复杂度理解_数据_07

六、过期策略篇

1. redis支持哪些过期策略?它使用的是哪一个?
	定时删除,惰性删除,定期删除三种,它默认使用的是定期删除+惰性删除
	
2. 谈谈定期删除策略的实现?
	为了解决效率问题,定期删除策略并不是遍历整个字典,它采用的是贪心算,具体的如下:
	2.1. 从过期字典中挑选出20个key;
	2.2. 删除20个key中已过期的key;
	2.3. 如果删除的key占比超过了1/4则重复步骤1;
	基于以上逻辑为了解决循环过度导致线程卡死的现象,在算法上加上了超时时间的机制,默认时间是25ms。

3. 如果订阅了key失效事件,key过期了收到的通知会有延迟么?为什么?
	会延迟,因为redis默认是惰性删除+定期删除,定期删除采用的贪心算法在淘汰key时会存在延迟问题。
	
4. 在集群中a这个key过期了,但是从slave读到a,它会返回什么给客户端?slave会马上删除a么?为什么?
	在redis3.2之前会返回key的值,redis3.2之后会返回给客户端null,salve不会马上删除a,当
a在master节点上被淘汰后,del指令同步给salve时salve才会删除a。

总结

本来只是想写一下常见面试题,但是写着写着之前列举的大纲就变了,这个就不定期的更新补充吧,如果发现以上内容有问题请告知我,谢谢!