Redis 高可用实现原理

Jan 8, 2022 22:45 · 1854 words · 4 minute read Redis

要想实现高可用最简单粗暴的方法就是多副本,多个实例保存同样的数据,这样即使一个实例出现故障,客户端就可以迅速切换至其他实例保证服务不中断。Redis 选择了读写分离的主从模式实现高可用:所有副本均可处理读操作;只有主库处理写操作,然后将写操作同步至从库。

etcd 通过 raft 协议保证每一次操作都能落实到大多数节点;而 redis 采取的是最终一致性策略,因为读写分离,客户端只能在主库执行数据修改,从库同步主库的数据,不需要在节点之间“协商”(raft 协议的性质决定了它会对性能造成巨大的影响),所以性能比 etcd 好太多。

主从同步

Redis 实例 B 通过 replicaof <masterip> <masterport> 配置选项(命令)将自己降级为另一个 Redis 实例 A 的从库,从实例 A 复制数据。

第一次同步为全量复制

  1. 从库给主库发送 PSYNC 命令 psync ? -1 通知主库启动主从复制,因为从库一开始不知道主库的 runid,所以用 ? 表示;偏移量设置为 -1 代表这是第一次复制。
  2. 主库收到 PSYNC 命令,应答 FULLRESYNC(全量复制)并带上主库的 runid 和偏移量 offset。
  3. 主库 bgsave 生成内存快照 RBD 文件并传输给从库,从库接受完快照文件立即清空已有数据并在本地加载数据。
  4. 在主从库传输快照期间实际上主库同时也在处理操作,主库将这些命令记录至复制缓冲区,在 RBD 文件传输完成后发送给从库,从库也执行这些命令实现主从同步。

主库在生成内存快照时会 fork 一个 bgsave 子进程(Copy-On-Write 技术),读取主进程所有的内存数据并写入 RBD 快照文件。虽然子进程在被创建后异步写文件,但是 fork 操作本身会阻塞主进程,如果大量从库同时全量同步,就会频繁阻塞主进程,严重影响 Redis 集群主库的响应速度。而且 Redis 实例一般会部署在不同的节点上,大量传输 RBD 文件也会占用主节点的网络带宽资源。

所以 Redis 推荐使用级联的主从模式部署,将从库的“主库”设置为集群中的一个高配置从库:

A —> B —> C

主从模式存在的风险在于网络分区会使从库中的数据版本落后于主库,而网络故障是不可避免的。Redis 2.8+ 版本在网络恢复后采取增量复制的策略来避免全量复制带来的巨大开销。

主库选举

主库挂掉会导致整个 Redis 集群完全不可写。Redis 采用“哨兵”(sentinel)来监控所有实例状态,周期性地给所有实例发送 PING 命令(心跳)并计算应答是否超时,如果主库出问题了,将某个从库升级为主库并通知集群中的实例和连接 Redis 集群的客户端发生主从切换事件。

判断主库下线

  • 主观下线 sdown sentinel 进程给 Redis 实例发送 PING 命令后未在指定时间内收到应答(PONG),会将其标记为“主管下线”。但是对待主库就要特别小心,因为存在误判,比如网络抖动或者主库因为任务繁重没有及时应答。
  • 客观下线 odown Redis 哨兵也遵循了 quorum 机制大多数哨兵都判断主库主观下线了,才将其标记为“客观下线”,而这个法定票数是用户自己设置的 sentinel monitor <master-group-name> <ip> <port> <quorum>,一般设置为 (集群节点数 N / 2 + 1)

选主

主库客观下线后,就要从多个从库中选择一个升级为新主库。

  1. 筛选 检查从库当前网络状态,并综合判断它的前科(之前的网络状态),如果它和主库断连的次数过多那肯定有问题,直接筛掉。通过 down-after-milliseconds 选项设置最大连接超时时间。适当调大 down-after-milliseconds,当哨兵与主库之间网络存在短时波动时,可以降低误判的概率;但也意味着主从切换的时间会变长,对业务的影响变大,我们需要根据实际场景进行权衡,设置合理的阈值。
  2. 打分,一共三轮,只要有一轮某个从库得分最高就会直接被选为主库,不会进入下一轮
    1. 从库优先级

      通过 slave-priority 参数给从库设置优先级,优先级最高的从库得分高

    2. 从库复制进度

      和主库同步进度最接近的从库得分高

    3. 从库 ID 号

      ID 号最小的从库得分高

通知

哨兵将新主库的地址写入自己实例的 pub/sub(switch-master)中,客户端需要订阅这个 pub/sub,可以感知到主库发生切换并拿到最新的主库地址。所以 redis 客户端一般会连接哨兵来获取主库相关信息。

哨兵集群

通过多个哨兵实例组成哨兵集群来实现哨兵的高可用。哨兵建立彼此的连接后,通过向主库发送 INFO 命令来获取从库列表,然后与所有从库建立连接,发送心跳检测从库活性。

执行主从切换需要票选出一个执行者哨兵。

  1. 任意哨兵实例在判定主库客观下线后,会给其他哨兵实例发送 IS-MASTER-DOWN-BY-ADDR 命令,其他实例会根据自己观察到的主库情况,投赞成/反对票。当这个哨兵获得了法定票数后,就有可以将主库标记为客观下线。
  2. 这个哨兵随后向其他哨兵发送命令希望由自己来执行主从切换,这就是哨兵的选举(哨兵如果没有给自己投票,就会把票投给第一个给它发送投票请求的哨兵)。但是哨兵之间的票选非常依赖于网络的质量,很容易出现多轮投不出 leader 的情况。

相关文档


redis