Redis的高可靠性
约 4090 字大约 14 分钟
2025-07-04
Redis的高可靠性指两方面:
- 数据尽量少的丢失
- 服务尽量少的中断
前者由AOF日志和RDB文件保证,对于后者Redis的做法是增加冗余副本,即将一份数据同时保存在多台实例上,即使有一台实例出现故障,其他实例也可以对外提供服务,不会影响使用。
主从库模式
冗余副本有很多份,那么Redis是如何保证多台实例上的数据是一模一样的呢? 实际上,Redis提供了 主从库模式,以保证数据副本的一致性,主从库之间采用的是读写分离的方式。
- 读操作:主从库都可以接受
- 写操作:首先在主库执行,然后,主库将写操作同步给从库
主从库之间的第一次同步
- 启动多台Redis实例时,它们相互之间可以通过
replicaof
(Redis5.0前使用的是slaveof)命令形成主库和从库的关系。
// 运行该命令的Redis实例就成为了 172.1.1.0 服务器中Redis的从库
replicaof 172.1.1.0 6379
- 建立主从库管理后就会进行第一次同步 2.1. 主从库建立链接、协商同步的过程,主要是为全量复制做准备。 从库和主库建立起链接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。 具体而言,从库执行replicaof命令后,会给主库发送psync命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。 psync命令包含了
主库的runId
和复制进度offset
两个参数
runID: 每个Redis实例启动时都会自动生成一个随机ID,用来唯一表示这个实例。主从库第一次同步时,从库并不知道主库的runID,所以将参数为 ? offset: 第一次设置为-1,表示为第一次同步 2.2. 主库收到psync命令后,会用FULLRESYNC响应命令带上两个参数:主库的runID和主库目前的复制进度offset,返回给从库。从库收到响应后会记录下这两个字段 ps:有FULLRESYNC响应就表示这是第一次同步,主库会将当前内存中所有数据同步给从库 2.3. 从库收到主库的RDB文件后会清空本地的所有数据再完成数据加载 2.4. 主库在发送RDB文件后的新的写命令记录在 replication buffer 缓冲中,当主库完成RDB文件的发送后会将缓冲中的命令发个从库。
当完成主从库第一次全量同步之后,主从库之间会一直维护一个网络连接,主库会通过这个长连接将后续收到的命令同步给从库。 这个过程叫做基于长连接的命令传播
主从级联模式
主从库之间的第一次同步是通过RDB文件进行数据全量复制的。 通过了解第一次同步的过程可以知道,主库需要完成两个耗时的操作:RDB文件的生成和RDB文件的传输。 虽然RDB文件的生成是由子进程完成的,但是fork出这个子进程的时候是会阻塞主线程的。传输RDB文件也会影响到主库的网络带宽。这样就会给主库带来很大的压力。
为了解决这个问题,于是就有了主-从-从
模式。 即:在部署主从集群的时候,可以手动选择一个从库,给这个从库再配置一些从库,这样,丛从库的同步压力就分担到了从库上,从而有效减少了主库的压力。
主从库网络间断的处理
在Redis2.8之前,Redis的处理是每短一次连接主从库会重新进行一次全量同步,开销非常大。 2.8之后,网络断开之后,主从库会采用增量复制的方式继续同步。 增量存在于 repl_backlog_buffer 缓冲区中,只要主库存在从库,主库所有的写命令处理传播给从库之外,都会在这个缓冲区记录一份,缓存起来。
**repl_backlog_buffer:**是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己读取到的位置。 一开始的时候,主从库的写读位置在一起,随着主库不断的接受命令,写位置会逐渐偏离起始位置,这个偏移量就是 master_repl_offset 。 从库在复制完写操作命令后,它在缓冲区中的读位置也开始逐步偏移刚才的起始位置,此时,从库已复制的偏移量 slave_repl_offset 也在不断增加。正常情况下,这两个偏移量基本相等。
replication buffer: Redis不管是和客户端通信还是和从库通信,都是通过内存buffer 进行数据交互。 每一个客户端脸上Redis后,redis都会为其分配一个内存buffer,Redis先把> 数据写入缓冲区中,然后再把缓冲区的数据发送到客户端Scoket中再通过网络发送出去。 所以主从在增量同步时,从库作为一个client,也会分配一个buffer,只不过这个buffer专门用来传播用户的写命令到从库,保证主从数据一致,我们通常把它叫做replication buffer。
主从库重连后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距。 在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset 会大于 slave_repl_offset。此时,主库只用把 master_repl_offset 和 slave_repl_offset 之间的命令操作同步给从库就行。
注意: 因为repl_backlog_buffer是环形缓冲区,如果从库读取的速度慢于主库写的速度,就可能会导致从库还未读取的操作被主库新写的操作覆盖了,这就会导致主从库数据不一致了。 为避免这种情况,可以通过调整 repl_backlog_size 这个参数。它和缓冲空间的大小有关,计算公式如下 :
缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小
Redis的持久化
作为了一个内存数据库,如果服务器一旦宕机,可能会导致所有redis数据丢失,为了防止这种情况的发生,redis提供了两种持久化的方式,分别是AOF和RDB
有一种说话:反正是缓存,其实可以直接从数据库的进行恢复 确实可以这样,但是这样会增加源数据库的压力,并且也没有从内存中获取数据来的快
AOF
AOF日志:它是一种写后日志。即:Redis先执行命令把数据写入内存,再记录日志。这样杜绝了以下几个问题。 1、防止AOF日志记录错误命令的日志,命令执行失败后是不会记录日志的 2、命令执行后再记录日志,不会阻塞当前的写操作。 存在的风险: 1、写入操作后,日志还没来的及写入服务器就宕机了会导致该条数据丢失。2、AOF虽然避免了当前阻塞当前命令的执行,但是AOF日志是在主线程中执行的,如果AOF写入压力大会阻塞后续的操作。 这两个风险其实都和AOF日志写回磁盘的时机又关,因此AOF机制提供了适用于不同场景的三种写回策略。
三种写回策略
- Always:同步写回,每个写命令执行完,立马同步的将日志写入磁盘
- Everyser: 每秒写回,每个写命令执行完后,先把写日志写到AOF文件的内存缓冲中,每隔一秒钟写入磁盘
- NO:操作系统控制写回,每个写命令执行完后,写吧日志写到AOF文件的内存缓冲中,何时写入磁盘由操作系统决定。
AOF日志的重写机制
理论上是每次写命令都会生成一条AOF日志,随着时间的推移,AOF日志会越来越大,如果还是用它作为故障恢复的手段,整个恢复就会非常的缓慢,这就产生一个新的机制:重写机制
重写机制其实就是将原本的AOF日志进行多变一,多是指原日志,一个key可能被多次操作,会有多次日志,重写后会只有一条日志,只记录当前状态的key-value。
AOF重写会阻塞吗?
AOF日志IDE写回是使用的主线程,但是重写过程是由后台子进程bgrewriteaof 来完成的,这样就避免了主线程阻塞。 重写的过程:一个拷贝,两处日志
触发AOF重写的时机
- auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
- auto-aof-rewrite-percentage: 这个值的计算方法是:当前AOF文件大小和上一次重写后AOF文件大小的差值,再除以上一次重写后AOF文件大小。也就是当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。
- 手动发送“bgrewriteaof”指令,通过子进程生成更小体积的aof,然后替换掉旧的、大体量的aof文件。
内存快照 RDB(Redis DataBase)
所谓内存快照,就是指内存中的数据在某一时刻的状态记录。
给那些数据做内存快照
Reids的数据都在内存中,为了提供所有数据的可靠性保证,它执行的全量快照,也就是内存中的所有数据都记录导磁盘中。
redis提供了两个命令来生成RDB文件,分别是save
和bgsave
- save:在主线程中执行,会导致阻塞
- bgsave:由主线程fork出一个子进程,专门用于写RDB文件,避免了主线程的阻塞,也是Redis RDB文件生成的默认配置。
快照时数据能修改吗?
能。Redis在执行快照的时候,会借助操作系统的提供的写时复制技术(Copy-On-Write, COW)
。 示例:在进行快照的时候,有一个键值对A,此时主线程想修改A,此时,A会被复制一份,生成该数据的副本A’,然后主线程在这个数据副本上进行修改。同时 bgsave 子进程可以继续把原来的数据 即A写入RDB文件。
可以每秒做一次快照吗?
最好不要。其一、如果频繁的执行全量快照,会导致磁盘压力激增,因为频繁快照会频繁的将内存中全量的数据写入磁盘。可能会导致上一次快照还没结束下一次快照又开始了,是一个恶性循坏。 其二、bgsave子进程是由主线程中fork出来的,虽然子进程在创建后不会导致主线程阻塞,但是fork子进程这个过程会导致主线程的阻塞。如果频繁的fork bgsave子进程就会阻塞主线程了。(redis中如果已经拥有一个basave子进程了就不会再启动一个bgsave子进程)
Redis推荐的持久化方式
Redis4.0中提出了 混合使用AOF日志和内存快照的方法。
哨兵机制
为确保当主库宕机后,Redis集群依旧可以正常提供服务,Redis提供了哨兵机制
主库挂了之后,从库能够提供读操作,但是没有主库就不能为客服端提供写操作,哨兵机制用于 确定主库真的挂了、推举一个从库作为新的主库向外提供写操作等 哨兵负责的三个任务:监控,选择主库,通知
哨兵机制的基本流程
监控: 哨兵会周期性给所有主从库发送PING命令,检测Redis服务器是否在线运行。 如果从库在规定时间内响应哨兵的PING命令,哨兵就将它标记为“下线状态”,该从库就不再向外提供读操作。 同样,主库在规定时间内没有响应哨兵的PING命令,哨兵就会判定为主库下线,然后开始 自动切换主库 的流程。
选主: 主库挂掉之后,没有在规定时间内响应哨兵的PING命令,哨兵们就会在所有从库中选择一台作为新的主库。 具体流程,在后面
通知: 新主库上位后,哨兵会将新主库的连接信息发给其他从库,让它们执行 replicaof 命令,和新主库建立链接,并进行数据复制。 同时,哨兵也会将该信息发给客户端。
主库的主观下线和客观下线
主观下线: 如果哨兵发现主库或者从库对PING命令的响应超时了,这个时候 哨兵就会把它标记成“主观下线” ps:如果检测的是从库,哨兵简单的把它标记成是 主观下线。
如果检测的是主库,哨兵不能直接的将主库标记为主观下线,开启主从切换,因为,有可能是哨兵误判了,其实主库并没有故障,但是启动主从切换会导致额外的计算和通信开销。
为了避免误判的情况,哨兵通常会采用多实例组成的集群模式进行部署,它们被称之为哨兵集群。 当有一个哨兵判断判断主库为下线状态后,会通知其他哨兵也对主库发送PING命令,当大多数哨兵判断为主库为“主观下线”状态后,主库才会被标记为“客观下线状态”。
选定新主库
哨兵选择新主库的过程称之为 “筛选+打分” 。 哨兵集群会从多个主库中按照 一定的筛选条件(如:已下线,总是断连) 把不符合条件的从库过滤掉,再通过一定的规则(如:同步速率最快)对剩下的从库进行打 分,然后将得分最高的从库作为新主库。 三轮打分: 从库优先级、重复复制进度、从库ID号。
基于pub/sub 机制的哨兵集群
部署哨兵集群只需要设置 主库的ip和端口号,并没有配置其他哨兵的连接信息。那么那么多哨兵是如何组成集群的呢?
sentinel monitor <master-name> <ip> <redis-port> <quorum>
哨兵之间可以相互发现,是借助与Redis 的发布与订阅机制。 哨兵只要和主库建立起了连接,就可以在主库上发布消息和订阅消息了。 在主从集群中,主库上有一个名为 “_ sentinel _:hello” 的频道,不同哨兵就是通过这个频道来相互发现与通信的。
哨兵获取从库的ip地址和端口的方式是想主库发送INFO命令,主库会将从库列表中返回给哨兵。
其他知识点
基于 pub/sub 机制的客户端事件通知
客户端可以通过订阅不同的频道,获取不同的订阅信息。如:实例进入主观下线频道 +sdown ; 退出主观下线 -sdown;进入客观下线 +odown;退出客观下线: -odown;新主库切换: + switch-naster 等
哨兵选举
当发生主库切换的事件时,选择那个哨兵进行主从切换的机制。通过投票的方式来选举,一个哨兵只能选一个哨兵头头,时间优先,少数服从多数