Redis 面试 —— 主从、持久化、淘汰的原理

1、为什么使用 Redis

在前面的几篇文章里,介绍了如何在 Spring 框架下集成 Redis,但是,我们为什么要是用 Redis 呢???这里就必须提到 Redis 的几个优点了。Redis 是基于内存存储的,同时,是 单进程 单线程 模型的 KV 数据库,使用 C 语言实现的(比 JAVA 更高效的语言),官方声称可以达到 10w+QPS(每秒可以查询的次数)。

因为是基于内存存储的,所以绝大部分的处理都是内存操作,也就必然会比存在 IO 操作的数据库快;键值对的存储方式,使得对数据的处理类似 HashMap,我们知道 HashMap 增删改的时间复杂度都是O(1)。

因为采用了 单进程 单线程 模型(使用 多路 I/O 复用 模型,非阻塞 IO),避免了多进程、多线程之间的切换而消耗的 CPU 时间片,同时也不用考虑资源的竞争问题,也就没有可能因为死锁导致的性能消耗。

2、集群部署

Redis 是单进程单线程的,同时也提到了 Redis 的 QPS 是 10w+,那是不是说明 Redis 是有瓶颈的,想想双 11 的晚上,这个明显是瓶颈,应该怎么办???

可以采用集群的部署方式,例如 Redis cluster,并且采用主从同步读写分离,Redis cluster 支持多个 master 节点,每个 master 节点下挂载多个 slave 节点。这样种模式下 Redis 可以很容易的横向扩容,假如需要支持更大数据量的缓存,横向扩容更多的 master 节点即可。

3、缓存是怎么持久化的

持久化是 Redis 高可用的原因之一,我们知道 Redis 数据是存在内存的,假如没有持久化,机器重启了,内存的数据就会丢失,大家不会主动的重启,可是服务器有宕机的风险,这个需要考虑。Redis 的持久化,主要有两种机制 RDBAOF

RDB

这种机制是对 Redis 中的数据进行周期性的持久化(写入磁盘)。

优点:生成多个时间节点的数据文件,每个数据文件分别代表某一时刻 Redis 里的数据,这种方式只要设置定时任务,周期性的将数据文件备份到其他服务器上。这以后,如果线上的服务器挂了,去备份服务器上拷贝一份之前的数据即可。RDB 对 Redis 的性能影响非常小,同步数据的时候,因为只是 fork 了一个子进程做持久化,且这种方式在数据恢复的时候速度比 AOF 的方式快。

缺点:说白了 RDB 就是某一时刻的快照,一般都是默认五分钟或者更久的时间备份一次,意味着你这次备份与下次备份时间间隔内的数据可能会丢失掉。AOF 是操作日志的记录,相对来说会好很多。

AOF

AOF 则是为每条写入命令作日志记录,以 append-only 的模式写入到日志文件。在 Redis 的配置文中可以设置不同的 AOF 持久化方式,它们分别是 appendfsync always(每次有数据修改都会记录,会严重降低 Redis 的速度)、appendfsync everysec(每秒同步一次,将多个写命令同步到硬盘)、
appendfsync no(让操作系统决定何时进行同步)。一般为了兼顾数据的完整性和写入性能,使用 appendfsync everysec 选项 ,最坏的情况也就是丢失一秒内的数据。

优点:相较 RDB 的方式最多丢一秒的数据。AOF 在对日志文件操作时是以 append-only 的方式进行的,只是追加的方式写数据,没有磁盘寻址的开销,写入高效,文件也不容易损坏。

缺点:一样的数据下,AOF 的文件比 RDB 的文件大。

注:AOF 开启后,Redis 支持写的 QPS 会比 RDB 方式下低,毕竟,每秒都会异步刷新一次行为日志,即使这样性能依旧很高。两种机制全部开启的时候,Redis 在重启的时候会默认使用 AOF 重新构建数据,因为 AOF 的数据是比 RDB 更完整的。

Redis 4.0 对于持久化机制的优化

Redis 4.0 支持 RDB 和 AOF 的混合持久化(默认关闭,需要通过配置 aof-use-rdb-preamble 开启)。单独使用 RDB 会丢失很多数据,单独用 AOF,数据恢复没有 RDB 的方式快。真出事的时候,先用 RDB 恢复,再用 AOF 做数据补全。如果采用混合持久化方式,AOF 重写的时候,会把 RDB 的数据内容写到 AOF 文件开头。

AOF 重写:生成一个新的 AOF 文件,新的 AOF 文件比原来的 AOF 文件体积更小,但是数据库状态是一样的。这个过程,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新的 AOF 文件时,记录所有修改数据的行为。当子进程完成新的 AOF 文件后,服务器会将重写缓冲区中的所有行为日志追加到新的 AOF 文件末尾,使新旧 AOF 文件所保存的数据状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,这样就完成 AOF 文件的重写。

4、Redis 的高可用

Redis 可以使用哨兵保证集群的高可用。建议使用用三个以上的哨兵实例去保证健壮性。但是需要注意,哨兵+主从并不能保证数据不丢失。

说道要使用三个以上的哨兵实例,这个是为什么呢???先看看示意图
在这里插入图片描述
M1:主 Redis 实例
S1、S2:从 Redis 实例 1、2
O1、O2、O3:哨兵实例 1、2、3

一般来说,哨兵实例和 Redis 实例会部署在一台机器上,当 M1 挂了,哨兵会根据选举策略,推选出一个哨兵的 leader,由 leader 控制故障迁移的问题,哨兵选举 leader 需要多个哨兵实例支持。如果是少于三个哨兵实例的情况,碰到整个机器都挂了的时候,就比较头疼了,没有了 leader 选举,故障迁移也不能顺利的执行。

哨兵的主要功能:
1、集群监控:监控 Redis 主从实例是否正常工作。
2、消息通知:当某个 Redis 实例发生故障时,发送报警消息通知管理员。
3、故障转移:当 master 实例挂了,自动将当前 master 节点下 的 某个 slave 节点升级为 master 节点,其他 slave 节点从新配置主从关系。
4、配置中心:当故障迁移发生时,通知客户端新的 master 节点地址。

5、主从实例间的数据如何同步

首先需要明确主从架构的好处,前面提到过单机 Redis 实例 QPS 有 10w+ 的上限,使用 Redis 的目的就是为了读的高并发,一台机器又读又写,必然会有性能的损耗,通过主从的架构,机型读写分离,master 实例的机器写,数据同步给 slave 实例的机器,由 slave 实例负责读,这样可以分发大量的读请求,从而提高整体的性能。

主从实例之间是如何进行同步的???

slave 实例启动的时候,会发送一个 psync 命令给 master 实例,如果是 slave 实例是第一次连接到 master 实例,会触发一个全量复制。master 实例会开辟一个子进程,生成 RDB 快照,同时把新的有关写的请求缓存起来,RDB 文件生成完成后,master 实例会把这个 RDB 文件发送给 slave 实例,slave 开始将 RDB 的数据写进本地磁盘,而后再加载到内存,之后 master 实例会将那些缓存的新的操作记录发送给 slave 实例,从而完成完整的同步过程。不需要担心传输过程中断网宕机的情况,因为 slave 实例会自动重连,并且连接成功后会把缺少的数据补上。

注:需要明确一个问题,RDB 快照文件生成的同时,必须缓存新的请求,不然同步的数据可能不完整。

6、淘汰机制

Redis 存在淘汰机制,淘汰机制可以有效的控制内存大小,当内存空间使用空间超过最大内存(通过 redis.conf 的 maxmemory 参数设置最大使用内存,0 表示没有内存限制)的时候,就会根据设定的策略自动淘汰老的数据。那么为什么会有淘汰机制呢???

使用 Redis 的时候,可以为存储的 value 设置一个过期时间。这点很好理解,例如我们的手机 app,长时间没有打开,会强制再次登录,还有短信验证码,因为长时间保存这些信息,没有意义,还会造成不必要的内存消耗。设置过期时间可以使用 expire 接口,或者调用包含过期时间的 set 接口。Redis 有两种删除数据的方式 定期删除惰性删除

定期删除:每隔 100ms 随机抽取一些设置了过期时间的 key,检查是否过期,如果过期了就删除。问题来了,为什么是随机抽取而不是遍历所有数据,毕竟遍历才能删除的最干净?可是反过来想一想 Redis 可是存着几十万个 key,每次都遍历所有的设置过期时间的 key,无疑会给 CPU 带来巨大的负载!
惰性删除:单纯使用定期删除,很有可能导致很多已经过期的 key 依旧没有被删除,惰性删除就是一个很好的补充。假如一个过期的 key,没有在定期删除的时候删除,之后,有一个新的请求过来,在 Redis 中查这个 key,发现它已经过期,便把这个 key 删除。

如果你是个心思缜密的程序员,你应该会发现,这里还有一个问题;那就是可能某个 key 已经过期了,定期删除没有删除它,但是,后续没有有关这个 key 的请求了,没有触发多行删除,那是不是就会一直在内存里面了。这就需要淘汰机制了。

前面讲了会根据一定的策略淘汰老的数据,那都有什么策略呢

Redis 3.0 支持的策略
noeviction:不删除策略,达到最大内存限制时,如果需要更多内存,直接返回错误信息。
allkeys-lru:在所有 key 中,优先删除最近最少使用的 key。
volatile-lru:在设置了超时时间的 key 中,优先删除最近最少使用的 key。
allkeys-random:在所有 key 中,随机删除一部分 key。
volatile-random:在设置了超时时间的 key 中,随机删除一部分 key。
volatile-ttl:在设置了超时时间的 key 中,优先删除剩余时间短的key。

Redis 4.0 增加的策略
volatile-lfu:在设置了超时时间的 key 中,删除最不经常使用的 key。
allkeys-lfu:当剩余内存不足以容纳新写入的数据时,在所有 key 中,删除最不经常使用的 key。

注:如果 key 没有设置超时时间,不满足先决条件,那么 volatile-lru、volatile-random 和 volatile-ttl 策略的行为,和 noeviction 基本上一致。

淘汰实现的原理

Redis 是怎么淘汰数据的,淘汰数据使用的是什么算法呢???

Redis 收到一个写请求,检查这次请求对内存的使用情况, 如果超出最大内存(maxmemory 设置的上限),就根据设置的策略淘汰部分 key。整个过程中,因为淘汰和引发淘汰请求交替出现,内存使用量也会不断地超过上限,然后又低于上限的波浪形变化。

Redis 使用的是近似 LRU 算法,但又不是完全的 LRU 算法,这个前面也提到过了,要遍历全部的数据并不高效。被淘汰的 key , 并不一定是最满足 LRU 特征的 key,从设置了超时的 key 中抽取少量样本, 然后用 LRU 算法删除样本中的 key。

发布了34 篇原创文章 · 获赞 34 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_19154605/article/details/104268731