redis面试
面试需要掌握的点
- 是什么
- 数据类型
- 线程模型
- 内存管理
- 持久化机制
- 高可用方案(了解)
- 分布式锁原理
- 项目中的实际使用
是什么
答题思路:基本概念+项目中的使用场景+自己了解的知识点
C语言开发的,基于缓存的非关系型数据库,因为基于缓存所以读写速度非常快,常用来做缓存,支持多种数据类型XX可以满足很多业务场景。并且还支持事物、持久化、多种集群方案等。我在项目上用的比较多得场景是用来做一些信息的缓存,例如订单信息的缓存、字典信息的缓存等。也常用来做接口防刷(利用redis的setNX setEX这个命令,对应到redisTemplae就setIfAbsent,expire方法)、String的incr生成订单号。还有像分布式锁这些也用过,对redis的线程模式、内存管理、持久化方案也有一定了解。
数据类型
项目上
- list:元器件同步的时候,首先定时任务会同步元器件的id信息,然后通过mq异步的去插入基本信息,基本信息插入完之后吧id集合存redis的list。list可以很轻松的实现栈和队列。例如通过rpush,lpop就能实现队列。为了保证我放入redis的信息的有序处理,这个时候我其实及时用的队列的方式,吧已经同步完的元器件lpop出去。
- String:缓存订单信息,incr生成订单号的某个序列
常用的一些方法需要说的出来
- String
SET key value
GET key
INCR key
SETNX key value
EXPIRE key seconds
DEL key - list
rpush,lpop,lpush,rpop - hash
hset,hget… - set
- zset
线程模型
背
redis里面有一个文件事件处理器,这个文件事件处理器是单线程的,所以我们说redis是单线程模型(reactor模式等面试官问再去拓展)。它里面有一个IO多路复用程序,它会去监听多个socket连接的读写事件,并交给文件时间分派器去处理,文件事件分派器会根据不同的读写请求交给事件处理器去处理(命令请求处理器,命令应答处理器,连接应答处理器,主从连接处理器)
内存管理
背
如何判断过期 -》过期删除策略-》内存淘汰机制
如何判断过期 ?
Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)
过期删除策略
- 定期删除:只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除
- 惰性删除:每隔一段时间会抽取一批过期的key进行删除。一般是定期和惰性组合
内存淘汰机制
一共8种类。
用的最多的是all-keys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key
持久化
- RDB
照快照的方式,获取某个时间点数据信息,可以在redis.conf文件中配置,默认1分钟1000个key,5分钟10个key,15,1。会通过bgsave命令保存快照信息(过程是先写入一个临时文件,完成之后覆盖原来的文件)。优点是恢复速度快,只要把RDB文件加载到内存中就可恢复,但是会丢失最后一次快照之后的数据。
还有就是rdb的时候redis会fork出一个子进程,如果数据集比较大可能会造成redis毫秒级不能提供服务。 - AOF:在增删改的命令的时候,会先将这些命令一起写入内存缓存,根据配置来决定什么时候写入aof文件,配置有三种:always,everysec,no.一般都设置为每秒同步一次。这种方式的有点是数据丢失少,最多丢失一秒。缺点是恢复慢,因为恢复相当于把这些命令全部重新执行一遍。
redis实现分布式锁原理
回答思路
解决什么问题,单节点的时候可以通过synchronized锁,多节点服务是在不同的进程里面所以就没办法依赖synchronized,redis在处理网络请求的时候又是单线的。所以。
先参考这个,不知道质量怎么样:https://zhuanlan.zhihu.com/p/135864820
参考2
参考3
为什么redis适合做分布式锁:因为它在处理网络请求的时候是单线程的。
思路:redisson-》为什么用redisson。
- 早期版本setnx(当key不存在的时候就能设置成功set key if not exisit)对应java里面就是setIfAbsent()这个方法+setex(设置超时时间):两个命令不是原子操作,例如setex完了之后服务器刚好宕机,setex就失败了,这就会导致死锁。
- set(key,random_value,nx,px):用一个命令来解决保证了原子性,任务超时了,锁被释放,导致并发问题。
redisson引入看门狗机制,后台会去启动一个线程去检查任务是否超时,如果超时,自动续期。 - 以及加锁和释放锁不是同一个线程的问题:例如有一个执行了11秒,锁过期时间为10秒。这个时候再去删除锁可能删的是另一个线程的锁。
解决办法:加一个uuid作为标识,删除锁的时候判断该标识。但是查询和删除又不是原子操作,还是存在问题(通过lua脚本判断和删除写到一个字符串里,传给redis就行) - 锁不可重入:如果已经设置了就不能再设置。使用redisson里面有一个计数器每获取一次锁就加1。
- 异步复制可能造成锁丢失(对redis的master节点加锁的时候,还没同步到slave,master就挂了,这个时候发生主从切换,新的master又重新获取到了锁,就导致锁获取了两次。而我们一个安全的锁不管在什么时候都只能有一个客户端持有),使用redLock解决。顺序向所有节点申请锁,有一个超时时间,例如有五个如果是获取三个节点成功则认为获取锁成功。且获取锁的时间不能超过锁的过期时间。redisson里就有redLock的实现
redis主从同步机制
master节点rw,slave读。哨兵完成主从切换。
1、从节点执行slaveof masterip port,保存主节点信息
2、从节点的定时任务发现主节点的信息,建立stocket连接
3、从节点发送信号,主节点发回,两边相互通信
4、连接建立后,主节点将所有数据发送给从节点(数据同步)
5、主节点把当前数据同步给从节点后,便完成了复制过程。接下来主节点会持续把写命令发送给从节点,保证主从数据一致性。
rundId:每次redis节点启动的时候都会生成唯一的uuid,每次redis重启后,rundid都会发生变化
offset:主从节点各自维护复制偏移量offset,当主节点有写入命令时,offset=fffset+命令字节长度。从节点收,到主节点发送的命令后,也会增加自己的offset,并把自己的offset发送给主节点。主节点同事保存自己的offset,通过对比offset来判断主从节点数据是否一致。
repl_backlog_size:保存在主节点上的一个固定长度的先进先出队列,默认为1M。
全量复制:
- 从节点发送psync命令,psync runid offset(第一次,runId为?,offset 为-1)
- 主从节点返回fullresync runid offset,runid为主节点runid,offset为目前的offset。从节点保存信息
- 主节点启动bgsave命令fork子进程进行rdb持久化
主节点将rdb文件发送给从节点,到从节点加载数据完成之前,写命令写入缓冲区
- 从节点清理本地数据并加载rdb,如果开启了aof会从写aof
部分复制:
1、复制偏移量:psync runid offset
2、复制挤压缓冲区:当主从节点offset的差距超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
- 如果从节点保存的runid与主节点现在的runid相同,说明主从节点同步过,主节点会继续尝试使用部分复制(到底能不能复制还要看offset和复制积压缓冲区的情况)
- 如果从节点保存的runid与主节点的runid不同,说明从节点在短线钱同步的redis节点并不是当前主节点,只能进行全量复制。
过程原理:
redis高可用方案
参考
https://juejin.cn/post/7097521572885299214#heading-14
主从
Redis2.8之前使用sync[runId][offset]同步命令,Redis2.8之后使用psync[runId][offset]命令
- runId:每个Redis节点启动都会生成唯一的uuid,每次Redis重启后,runId都会发生变化
- offset:主节点和从节点都各自维护自己的主从复制偏移量offset,当主节点有写入命令时,offset=offset+命令的字节长度。从节点在收到主节点发送的命令后,也会增加自己的offset,并把自己的offset发送给主节点。这样,主节点同时保存自己的offset和从节点的offset,通过对比offset来判断主从节点数据是否一致
- repl_back_buffer:复制缓冲区,用来存储增量数据命令
过程:
- 首先slave发送psync命令要求master同步数据
- master收到psync命令后,会通过bgsave命名生成RDB快照文件,同时会继续接收客户端的请求,并写入buffer缓冲区
- RDB生成完毕后master会把rdb文件发给slave
- 然后再将之前写到缓冲区的命名也发给slave
- slave节点在载入master的数据之前会先把旧的数据清楚
- 然后把master发送的rdb文件件信息和缓冲信息加载到内存
- 后续master和slave会保持长连接,持续把写命令发给slave
哨兵
答题思路
哨兵能干啥-》哨兵机制原理
常用的架构,一主两从三哨兵
哨兵四大作用
- 集群监控:master和slave的进程是否正常工作
- 消息通知:redis的实例发生故障的时候,发送消息通知管理远
- 故障转移:master节点挂了会选举出新的slave作为master
- 配置中心:发生故障转移之后它会通知客户端新的master地址
哨兵同步原理
讲清楚:哨兵如何发现master,slave,哨兵如何互相发现、如何判断实例的主观下线和客观下线如何判断。
- 哨兵如何发现master和slave:每个哨兵每隔10秒会给主节点和从节点发送info命令,获取主从节点的级联机构信息。
- 哨兵之间的相互发现:通过发布订阅模式,每个哨兵每隔2秒向redis数据节点的频道上发送该哨兵对于主节点的判断和当前哨兵节点的信息,同时每个节点也会订阅该频道。
- 主观下线:每隔1秒哨兵会向主节点,从节点以及其余哨兵节点发送一次ping命令做心跳检查,通过判断ping回复是否是有效回复来判断实例是否在线,哨兵里面有一个配置down-after-milliseconds设置判断主观下线的时间长度,如果在这个时间范围内都是无效回复,则认为实例已主观下线。
- **客观下线:**如果被判断主观下线的服务是master节点,这时哨兵会通过一个命令寻求其他哨兵节点对主节点的判断,如果一半以上的哨兵都判断服务已下线,则这个时候为客观下线。这个时候会选举出新的主节点(选举判断依据:根据设置的优先级,runId大小,offset信息),并且把剩余节点指向新的主节点进行数据复制
cluster(集群)模式
它使用数据分片(sharding)引入哈希槽(hastslot)来实现,一个redisCluster包含16384个哈希槽,存储在redidCluster中的所有键都会被映射到这些slot中,slot=CRC16(key)%16384来确定key属于哪个哈希槽。