Redis相关面试题汇总
天行健,君子以自强不息;地势坤,君子以厚德载物。
每个人都有惰性,但不断学习是好好生活的根本,共勉!
文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。
梦绕边城月,心飞故国楼。
思归若汾水,无日不悠悠。
——《太原早秋》
文章目录
- Redis相关面试题
- 1. redis介绍
- 优点
- 缺点
- 2. 常用基本数据类型
- 3. 持久化方式
- 3.1 RDB持久化
- RDB保存策略
- 3.2 AOF持久化
- AOF保存策略
- 3.3 策略选择
- 4. redis快的原因
- 4.1 基于内存
- 4.2 数据结构简单
- 4.3 单线程
- 4.4 非阻塞IO
- 拓展
- 4.5 底层模型
- 5. redis使用单线程的原因
- 6. redis服务器内存大小
- 7. redis操作如何保证原子性
- 8. redis中的事务
- 9. redis数据和MySQL数据库如何实现一致性
- 9.1 延时双删策略
- 9.2 设置缓存的过期时间
- 9.3 写完数据库后,如何成功删除缓存
- 9.3.1 方案一
- 9.3.2 方案二
- 10. 缓存击穿/缓存穿透/缓存雪崩的原因和解决方案
- 10.1 缓存击穿
- 解决方案
- 10.2 缓存雪崩
- 解决方案
- 10.3 缓存击穿
- 解决方案
- 11. 哨兵模式
- 11.1 下线
- 主观下线
- 客观下线
- 11.2 工作原理
- 12. Redis常见性问题和解决方案
- 13. redis热点数据
- 13.1 redis内存的淘汰策略
- 13.2 数据淘汰策略
- 14. Redis集群方案
- 14.1 twemproxy
- 14.2 codis
- 14.3 redis cluster 3.0
- 14.4 业务层代码实现
- 15. redis哈希槽
- 16. redis适用场景
- 16.1 会话缓存 Session Cache
- 16.2 全页缓存 FPC
- 16.3 队列
- 16.4 排行榜/计数器
- 16.5 发布/订阅
- 17. redis在项目中的应用
Redis相关面试题
1. redis介绍
Redis全称Remote Dictionary Server
优点
-
是一个key-value类型内存数据库,数据库加载在内存中进行操作,定期通过异步操作将数据flush到硬盘上进行保存
-
已知性能最快的键值类型数据库
-
支持多种数据结构
-
单个value最大限制1GB
-
List类型可用作FIFO双向链表,实现轻量级高性能的消息队列服务
-
Set类型可做高性能的tag系统
-
支持过期时间设置
缺点
数据库容量受到物理内存限制,不能用作大量数据的高性能读写,因此Redis适合场景为较小数据量的高性能操作和运算
2. 常用基本数据类型
-
字符串String
一个字符串类型最大存储容量为512M -
列表list
可以重复的集合 -
set
不可重复的集合 -
hash
类似Map<String, String> -
Zset
3. 持久化方式
两种方式持久化,RDB和AOF
- RDB
指定时间间隔将数据进行快照存储 - AOF
记录每次对服务器的写操作,以追加方式写到文件末尾,服务器重启时重新执行这些命令来恢复原始数据,redis会对AOF文件后台重写,防止该文件体积过大
可以选择开启一种或同时开启两种持久化方式
当同时开启两种持久化方式时,redis重启的时候会优先载入AOF文件来恢复原始的数据,AOF通常保存的数据比RDB方式保存的更完整,因为AOF记录每个操作,RDB是指定时间间隔进行快照保存,一般这个间隔会比较久
3.1 RDB持久化
redis单独创建一个子进程进行持久化,将数据先写到临时文件,持久化结束再用临时文件替换上次持久化好的文件,这个过程不会进行任何IO操作,确保了极高的性能,如需大量数据恢复,且不严格要求数据完整性,RDB比AOF更加高效
RDB保存策略
save 900 1
表示900秒内如果至少有1个key的值变化则保存
save 300 10
300秒内如果至少有10个key的值变化则保存
save 60 10000
60秒内如果至少有10000个key的值变化则保存
3.2 AOF持久化
redis每次重启时读取该文件(AOF),重新执行新建、修改数据的命令恢复数据
AOF保存策略
appendfsync always
每次产生一条新的修改数据的命令都执行保存操作,效率低,但是安全
appendfsync everysec
每秒执行一次保存操作,如果在未保存当前秒内操作时发生了端点,仍然会导致一部分数据丢失,即1秒钟的数据
appendfsync no
从不保存,将数据交给操作系统来处理,更快,也更不安全
推荐并且默认的是每秒fsync一次,这种fsync策略可以兼顾速度和安全性
AOF缺点
- AOF比RDB占用更多的磁盘空间
- AOF恢复备份速度比RDB慢
- AOF每次读写都同步的化,有一定的性能压力
- 存在个别bug,造成恢复卡滞
3.3 策略选择
可读的日志文本,通过操作AOF
官方推荐,如果数据不敏感可以单独使用RDB,不建议单独使用AOF,可能会出现BUG,如只做单纯内存缓存,可以都不用
4. redis快的原因
4.1 基于内存
redis完全基于内存,几乎所有请求都是内存操作,数据存储在内存中,类似HashMap,查找和操作的时间复杂度O(1)
4.2 数据结构简单
redis数据结构简单,对数据操作也简单,redis中的数据结构设计性能简单高效
4.3 单线程
redis采用单线程,避免了切换和竞争,更不存在多进程或多线程导致的cpu消耗,以及无需担心线程锁和线程锁带来的各种问题
4.4 非阻塞IO
使用IO多路复用模型
拓展
-
IO
输入输出 -
多路
多个输入输出通道(socket) -
复用
通过一种机制同时管理多个IO操作
IO多路复用是一种操作IO技术,可理解成用单个线程同时管理多个socket
多种IO多路复用技术如,select、poll、epoll
Redis在linux使用epoll机制
4.5 底层模型
redis直接自己构建了vm机制,一般的系统调用系统函数需要花费一定时间去请求
5. redis使用单线程的原因
redis基于内存操作,cpu不会限制redis性能,机器内存和网络带宽才是限制redis性能的因素
因此,单线程更容易实现,也不会因为单线程限制性能,所以redis利用队列技术将并发访问变为串行访问(单线程)
几乎所有请求都是内存操作
单线程避免线程切换和竞争(性能)
6. redis服务器内存大小
redis内存在数在配置文件中配置
不设置或设置为0则默认内存大小如下
32位下默认是3G
64位下不受限制
redis一般设置内存为最大物理内存的四分之三,即0.75
命令行设置
config set maxmemory <内存大小,单位字节>,服务器重启失效
config set maxmemory 获取当前内存大小
maxmemory是字节类型bytes,注意单位转换
7. redis操作如何保证原子性
- redis命令的原子性是指一个操作的不可以再分,要么执行,要么不执行
- redis操作的原子性原因是redis是单线程
- redis提供的api都是原子操作
- 多命令并发中如何实现原子性,使用redis事务或者redis+lua的方式实现
8. redis中的事务
- redis中的事务是一组命令的集合,这组命令要么都执行要么都不执行
- redis的事务的实现需要用到MULTI和EXEC命令
- MULTI是事务开始的命令
- EXEC是事务结束的命令
流程:
输入MULTI命令回车后返回OK表示开启redis事务
依次输入所需执行的命令,每条命令回车后会返回一个QUEUED,表示命令暂存
最后输入EXEC命令执行所有命令
返回的结果是跟输入命令一一对应的,成功都会返回OK
redis事务除了保证命令要么全部执行要么全部不执行外,还能防止事务中执行命令不会被其他命令插入
redis事务不支持回滚操作
9. redis数据和MySQL数据库如何实现一致性
9.1 延时双删策略
在写库前后都进行redis.del(key)操作,并设定合理的超时时间
- 删除缓存
- 再写数据库
- 休眠500毫秒(根据具体业务时间定)
- 再次删除缓存
500毫秒怎么确定的,评估自己业务读取逻辑的耗时,确保请求结束再删除
除此之外还要考虑redis和数据库主从同步的耗时
即休眠时间为读取数据业务逻辑的耗时+几百毫秒的主从同步耗时即可,如1秒
9.2 设置缓存的过期时间
给缓存设置过期时间是确保最终一致性的解决方案,所有的写操作以数据库为准,达到缓存过期时间,后面的读请求会从数据库中读取新值回填缓存
双删策略+缓存超时设置,如此一来最差的情况就在超时时间内数据存在不一致,又增加了写请求的耗时
9.3 写完数据库后,如何成功删除缓存
之前的两个方案都存在一个缺点-操作完数据库后,由于某种原因删除缓存失败,此时,可能出现数据不一致的情况
以下为具体的保障重试的方案
9.3.1 方案一
更新数据库数据
缓存因某某原因删除失败
将需要删除的key发送至消息队列
自己消费消息,获得需要删除的key
继续重试删除操作,直至成功
缺点
对业务线代码造成大量侵入
为避免这个缺点,在方案二中,启动一个订阅程序去订阅数据库的binlog,获取需要操作的数据
在应用程序中另起一段程序,获得这个订阅程序传来的消息,进行缓存删除
9.3.2 方案二
更新数据库数据
数据库将操作信息写入binlog日志中
订阅程序提取出所需要的数据以及key
另起一段非业务代码,获得该信息
尝试删除缓存操作,删除失败
将这些信息发送到消息队列
重新从消息队列中获取该数据,重试操作
10. 缓存击穿/缓存穿透/缓存雪崩的原因和解决方案
10.1 缓存击穿
查询一个不存在的数据,缓存中没有,然后去数据库中查询也没有,但查到的结果为空这个null值不会写在缓存,这就会导致每次请求这个数据都会这样去查数据库,访问能量很大的时候数据库会崩掉
利用这个不存在的key可以攻击应用
解决方案
空的结果也进行缓存,设置一个空对象,设定过期时间为较短时间,如3min,,或者使用布隆过滤器,redission框架中也有布隆过滤器
10.2 缓存雪崩
设置缓存时采用了相同的过期时间,导致某一时刻同时失效,请求全部发到数据库,数据库压力过大导致崩掉
解决方案
在原有的失效时间基础上增加一个随机值,如1-5分钟随机值,如此每个缓存的过期时间的重复率会降低,就很难引发集体失效事件
10.3 缓存击穿
对一些设置了过期时间的key,如果同时被高并发访问,此时key正好到了过期时间,失效了,这么多访问量直接落到了数据库上,称为缓存击穿
解决方案
分布式环境下使用分布式锁进行解决,如redis的setnx,或zookeeper的临时顺序节点等
11. 哨兵模式
master节点异常,则会进行master-slave切换,将其中一个slave作为master,将之前的master作为slave
11.1 下线
主观下线
Subjectively Down
简称SDOWN,指的是当前Sentinel实例对某个redis服务器做出的下线判断
客观下线
Objectively Down
简称ODOWN,指的是多个Sentinel实例在对Master Server做出SDOWN判断并且通过Sentinel is-master-down-by-addr命令相互交流之后,得出的Master Server下线判断,然后开启failover
11.2 工作原理
- 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他Sentinel实例发送一个PING命令
- 如果一个实例(instance)距离最后一次有效回复PING命令的时间超过down-after-milliseconds选项所指定的值,则这个实例会被Sentinel标记为主观下线
- 如果Master被标记为主观下线,则正在监视这个Master的所有Sentinel要以每秒一次的频率确认Master的确进入了主管下线状态
- 当有足够数量的Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态,则Master会被标记为客观下线
- 一般情况下,每个Sentinel会以每10秒一次的频率向它已知的所有Master,Slave发送INFO命令
- 当Master被Sentinel标记为客观下线时,Sentinel向下线的Master的所有Slave发送INFO命令的频率会从10秒一次改为每秒一次
- 若没有足够数量的Sentinel同意Master已经下线,Master的客观下线状态就会被移除
- 若Master重新向Sentinel的PING命令返回有效恢复,Master的主观下线状态就会被移除
12. Redis常见性问题和解决方案
- Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
- 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
- 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
- 尽量避免在压力很大的主库上增加从库
- 主从复制不要用图状结构,用单向链表结构更为为你顶,即 Master<-Slave1<-Slave2<-Slave3…
- 这样的结构方便解决单点故障问题,实现Slave对Master的替换,如果Master挂了,可以立刻启用Slave1做Master,其他不变
13. redis热点数据
mysql中有大量数据,如何保证redis中的热点数据
13.1 redis内存的淘汰策略
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略
13.2 数据淘汰策略
noeviction
返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入命令,但DEL和几个例外)allkeys-lru
尝试回收最少使用的键(LRU),使得新添加的数据有空间存放volatile-lru
尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放allkeys-random
回收随机的键使得新添加的数据有空间存放volatile-random
回收随机的键使得新添加的数据有空间存放,但仅限于过期集合的键volatile-ttl
回收在过期集合的键,并且优先回收存货时间(TTL)较短的键,使得新添加的数据有空间存放
14. Redis集群方案
14.1 twemproxy
类似一个代理方式,使用方法和普通redis相同,设置号他的下属的多个redis实例后,使用时在本需要链接redis的地方改为连接twemproxy
它会以一个代理的身份接受请求并使用一致性hash算法,将请求转接到具体的reids,并将结果再返回twemproxy
使用方式简单,跟redis相比只需要修改连接端口,对就项目扩展的首选方案
问题
twemproxy自身单端口实例的压力,使用一致性hash后,对redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点
14.2 codis
codis是目前用的最多的集群方案,基本和twemproxy一致的效果,但他支持在节点数量改变的情况下,旧节点数据可恢复到新的hash节点
14.3 redis cluster 3.0
redis cluster 3.0自带的集群,特点在于它的分布算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点
14.4 业务层代码实现
在业务层代码实现,启动几个不相关的resi实例,在代码层,对key进行hash计算,然后去对应的redis实例操作数据
这种方式对ahsh层代码要求比较高,考虑部分包括:节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控等
15. redis哈希槽
redis集群没有使用一致性hash,而是引入了哈希槽的概念
redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分哈希槽
16. redis适用场景
16.1 会话缓存 Session Cache
最常用的一种使用redis的情景是会话缓存,即Session Cache
用Redis缓存会话的优点在于,redis提供持久化
当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴
16.2 全页缓存 FPC
除了基本的会话token之外,redis还提供简单的FPC平台
关于一致性的问题,重启redis实例,因为有磁盘的持久化,用户不会看到页面加载速度的下降,这个改进类似PHP本地的FPC
16.3 队列
redis在内存存储引擎领域的一大优点是提供list和set操作,使得redis能作为一个很好的消息队列平台使用
rdis作为队列使用的操作类似本地程序语言如python对list的push/pop操作
16.4 排行榜/计数器
redis在内存中对数字进行递增或者递减的操作实现的非常好,集合set和有序集合sorted set也使得我们在执行这些操作的时候变得简单
16.5 发布/订阅
redis的发布订阅功能,。适用场景也是非常多,社交网络连接中使用,或者作为基于发布订阅的脚本触发器,甚至用redis的发布订阅功能来建立聊天系统
17. redis在项目中的应用
redis在项目中常见应用场景的有以下几个方面
- 作为i缓存,将热点数据进行缓存,减少和数据库的交互,提高系统的效率
- 作为分布式锁的解决方案,解决缓存击穿等问题
- 作为消息队列,使用redis的发布订阅功能进行消息的发布和订阅
具体场景需要具体项目来使用
感谢阅读,祝君暴富!