我们在生产中使用 Redis,如果只部署一个 Redis 实例,当该实例宕机,到恢复之前都不可用;虽说 Redis 一般都用来做缓存,但不可用给业务系统带来的影响也是不小的,流量大时甚至会导致整个服务宕机。所以 Redis 的高可用也非常重要,Redis 的高可用简单来说就是增加冗余副本,将一份数据保存在多个实例上;即使有一个实例宕机,其他服务仍然可以对外提供服务,不影响业务使用。
一. Redis 主从同步
Redis 提供了主从模式(一主多从)来提高 Redis 的可用性,主从库之间采用的是读写分离:
读操作:主从库都能接收
写操作:主库能接收,执行完后同步给从库
主从同步原理
首次全量同步
主从第一次同步会经历三个步骤:
(1)主从库建立连接,二者连接完成后开始同步。
(2)首次同步需要全量数据,主库会 fork 出一个子进程来生成 RDB 快照,接着将 RDB 文件发送给从库(不会阻塞主线程),从库收到后清空旧数据,最后加载 RDB 文件完成全量数据同步。
(3)在主库生成 RDB 后接收的命令会暂存到一块内存区域:replication buffer,当从库加载完 RDB 快照后,再将这块暂存的数据发送给从库执行,最终完成首次主从同步。
为什么要单独维护全量同步阶段的增量数据呢?
单独维护是为了保证命令执行的顺序性,这批增量数据需要等到 RDB 文件加载完后再发送给从库,否则会因为先后顺序不同导致主从不一致。
当完成首次同步后,主从之间维护一个长连接,后续写命令通过这个长连接进行同步。
长连接因为网络问题断开了期间的写命令会丢吗?
当发生网络分区导致长连接断开,主库也会将写命令暂存到一块环形的内存区域,等待连接恢复后将暂存的写命令发送给从库,保证主从一致。
做主从复制的作用是?
数据冗余:主从复制实现了数据的热备份;
高可用:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;
负载均衡:在主从的模式下,配合读写分离,可以大大提供 Redis 整体的吞吐量。
二. Redis 故障转移
主从模式能做到数据备份,也能支持读写分离,但一旦主节点宕机,需要人工介入切换主节点。
Redis 提供了哨兵机制保证 Master 出现故障时自动进行主从切换,也就是故障转移。
哨兵机制
哨兵节点的作用分为三点:监控,选主,通知;一般哨兵会集群部署,原因是为了保证哨兵的高可用和防止下线误判(下线误判在下面分析)。
哨兵实现故障转移原理
1. 哨兵监控
Sentinel 节点会监控 matser、slave 及其他 Sentinel 节点的状态。这个是通过 Redis 自身的 pub/sub 机制实现的。Redis 的哨兵一共有三个定时监控任务,来完成节点的发现与监控。
监控主从拓扑信息:每隔 10 s,每个 Sentinel 节点会向主从库发送 info 命令,来获取最新的拓扑结构;
Sentinel 集群节点之间交换信息:每隔 2 s,每个 Sentinel 节点会向 _sentinel_:hello 频道上发送自身的信息,以及对主节点的判断信息。这样,Sentinel 节点之间就可以交换信息。
节点状态监控:每隔 1 s,每个 Sentinel 节点会向 master、slave 及其他 Sentinel 节点发送 ping 命令做心跳检测(服务端回复 pong 代表节点正常),来判断这些节点是否可达。
2. 主观下线
Sentinel 每隔 1 s 会对数据节点发送 ping 命令做心跳检测,当节点超过 down-after-milliseconds 没有进行回复,Sentinel 会对该节点做失败判定,这个行为被称作主观下线。
主观下线,顾名思义是主观的,可能会误判,假设主观下线后就进行主从切换,实际主库并没有发生故障,后续的选主和通知操作会带来额外的开销。
发生误判的场景:网络拥塞、节点发生短暂网络分区,或是节点压力较大响应超时。
3. 客观下线
为了防止下线误判,只有当大多数的哨兵节点认为 master 下线才算真正下线,这个行为叫做客观下线。
客观下线过程:
(1) 当某个 Sentinel 节点发生判断主库“主观下线”后,会给其他哨兵实例发送 is-master-down-by-addr 命令,其他哨兵节点会根据自己和主库的连接情况,做出 Y(赞同)或 N(反对)的响应。
(2) 当哨兵获取到了“客观下线”所需的赞成票数后,就可以标记主库为“客观下线”,这个所需要的票数由 quorum 配置项决定(例如,现在有 5 个哨兵,quorum 为 2,当两个哨兵判断主服务器下线后则触发故障转移)。
4.Sentinel Leader 选举
当发生了客观下线后,哨兵节点集群就会选出一个 Leader 来进行实际的故障转移操作。Redis 使用 Raft 算法来实现哨兵领导者的选举,大致过程如下:
(1)哨兵节点设置主服务器为“客观下线”后,向其他哨兵节点发送命令,表明希望自己来执行主从切换,其他哨兵节点会进行投票。
(2)当哨兵节点拿到半数以上的赞成票且票数大于等于哨兵配置文件中的 quorum 值就会成为 Leader。
Leader 选举的投票逻辑很简单:在这一轮投票中,如果没有投过票就回复同意,如果投过票就回复拒绝。
(3)如果此过程没有选出 Leader 则会等待故障超时间的 2 倍时长,然后进入下一轮选举。
什么情况会选不出 Leader?
哨兵集群能够成功投票,很大程度上取决于正常的网络传输。如果网络压力大或短暂阻塞就可能导致没有哨兵节点拿到半数以上的票。而网络问题一般都会持续一小段时间,所以在没有选出 Leader 后会等待一段时间再进入下一轮。
5. 故障转移
选出哨兵的 Leader 后就会进行故障转移,也就是从 slave 中选出一个新 master 替换故障 master,主要有以下判断标准:
(1)跟 master 断开链接的时长:如果一个 slave 和 master 的断开链接时长已经超过 down-after-milliseconds 的 10 倍,那哨兵就会认为该 slave 不适合被选为 master。
(2)slave 的优先级配置:slave priority 参数越小,优先级越高。
(3)主从复制进度:当 优先级 相同时,哪个 slave 和 master 的数据越接近,优先级越高。
(4)run id:如果 优先级配置 和 主从复制进度 都相同,则哪个 slave 的 run id 越小,优先级越高。
选出 master 后,对它执行 slaveof no one 命令让其成为主节点,并对剩余 slave 节点发送命令让他们成为新 master 的从节点,最后和其他哨兵节点交换信息完成故障转移。
主从切换过程中,是否能对外正常提供读写服务?
如果采用读写分离,还是可以正常处理读请求,但是对于写请求,服务端就无法处理了。如果需要应对写请求,业务系统中可以将写缓存的操作改成异步或放到队列处理。
脑裂问题
如果碰巧客观下线也误判会发生什么?
会发生脑裂。
脑裂就是在主从集群中同时有两个主节点,他们都能接收写请求。而不同的客户端会往不同的主节点上写数据,甚至导致数据丢失。
Redis 的脑裂一般发生在主从切换时原主库假故障的场景下:
当主库因为一些原因无法处理哨兵节点的心跳检测时,就会被判定为“客观下线”,接着就会进行主从切换,但在主从切换完成之前,原主库又恢复服务,就又会处理写请求,当主从切换完成后通知客户端之前就会有两个主节点,即发生脑裂。
Redis 的脑裂可能会造成数据丢失,根本原因是 Redis 内部没有通过共识算法来维护多个数据节点的强一致性,因为强一致性的成本太大,而 Redis 主打性能,所以 Redis 放弃 C(一致性) 而选择 A(可用性)。