Redis 官方提供了两种高可用(High Avalilable, HA)的实现方式,分别是 Sentinel(哨兵) 和 Cluster
Redis 一些相关基础概念 #
事件 #
文件事件
服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。
Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器。
时间事件
所有时间事件都放在一个无序列表中
- 定时事件
- 周期事件
对时间事件与文件事件的处理都是同步、有序、原子地执行,事件间不会抢占
主从复制 #
作用:
- 数据冗余:实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 数据恢复:当主节点出现问题,由从节点提供服务,实现快速的故障恢复。(服务的冗余)
- 负载均衡:主从复制的基础上,配合读写分离,有主节点提供写服务,从节点提供读服务。尤其写少读多的场景
- 高可用:是哨兵和集群能够实施的基础
流程:
- 设置主节点的地址和端口
- 建立套接字连接
- 发送
PING
命令 - 权限验证
- 同步
- 命令传播
同步 #
从节点向主节点发送 psync
命令(Redis2.8以前是 sync
命令),开始同步
全量复制
用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。
- 从节点或主节点判断无法进行部分复制
- 主节点 fork 子进程执行 BGSave,在后台生成 RDB 文件,并使用一个缓冲区(复制缓冲区)记录从现在开始执行的写命令
- 主节点将 RDB 文件发送给从节点。从节点清除自己的旧数据,载入接收的 RDB 文件,将数据库状态更新至对应状态
- 主节点将复制缓冲区中的所有写命令发送给从节点,从节点执行写命令,更新状态
- 如果从节点开启了 AOF,会触发 bgRewriteAof,保证 AOF 文件更新至主节点的最新状态
全量复制属于重型操作,消耗 CPU、内存、硬盘 IO,消耗主从节点的带宽;从节点载入新 RDB 文件的过程是阻塞的,无法响应客户端命令
部分复制
用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点
-
复制偏移量:主从节点分别维护一个复制偏移量 offset:主节点每次向从节点同步了 n 字节数据后,将修改自己的复制偏移量 offset+N;从节点同步后也会修改。如果二者 offset 不同,则根据两个 offset 找到从节点缺少的数据
-
复制积压缓冲区:主节点内部维护一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区,命令传播时,不仅会将写命令同步到从节点,还会将写命令写入复制积压缓冲区。
- 由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。因此,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
- 从节点将 offset 发送给主节点后,主节点根据 offset 和缓冲区大小决定能否执行部分复制:
-
服务器运行ID (runid):每个 Redis 节点,都有其运行 ID,运行 ID 由节点在启动时自动生成,主节点会将自己的运行 ID 发送给从节点,从节点会将主节点的运行 ID 存起来。 从节点 Redis 断开重连的时候,就是根据运行 ID 来判断同步的进度:
- 如果从节点保存的 runid 与主节点现在的 runid 相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制
- 不同,说明从节点在断线前同步的 Redis 节点并不是当前的主节点,只能进行全量复制。
命令传播 #
主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。
repl-disable-tcp-nodelay
- yes:合并小的 tcp 包从而节省带宽,但是增加同步延迟
- no:master 会立即发送同步数据
一般系统对数据不一致容忍度较高,且主从节点网络状态不好时采用 yes
心跳检测机制会定时向主服务发送消息,保证主从服务器一直处于连接状态
哨兵 Sentinel #
Sentinel 是 Redis 2.8 之后提供的一种高可用方案,它通过引入一个 Sentinel 节点,它负责监听、发现故障节点以及故障恢复来提高 Redis 可用性。
在这之前,Redis 依赖常规的主从复制,Slave 节点会同步 Master 节点的数据,当 Master 节点发生故障的时候,需要维护者手动进行一些列繁琐的操作,将 Slave 节点提升为 Master 节点,并通知业务方连接到新的 master 节点。
为了解决这个问题,Sentinel 模式被提出来,它可以自动的识别故障节点并转移。其主要工作原理是当 Sentinel 节点发现一个节点故障后,会对这个节点执行主观下线,如果该节点是 master 节点的话,会和其他 Sentinel 协商,如果多数 Sentinel 节点认为该节点故障,则判定为客观下线,并选举一个 Sentinel 节点来执行故障转移。
哨兵模式可以认为是一种分布式系统,主节点的系统故障是在多个实例共同认可的情况下完成的;即使不是所有的哨兵实例都正常运行哨兵集群也能正常工作。哨兵模式的功能可以概括如下:
- 监控(Monitoring):持续监控 Redis 主节点、从节点是否处于预期的工作状态。
- 通知(Notification):哨兵可以把 Redis 实例的运行故障信息通过 API 通知监控系统或者其他应用程序。
- 自动故障恢复(Automatic failover):当主节点运行故障时,哨兵会启动自动故障恢复流程:某个从节点会升级为主节点,其他从节点会使用新的主节点进行主从复制,通知客户端使用新的主节点进行。
- 配置中心(Configuration provider):哨兵可以作为客户端服务发现的授权源,客户端连接到哨兵请求给定服务的 Redis 主节点地址。如果发生故障转移,哨兵会通知新的地址。这里要注意:哨兵并不是 Redis 代理,只是为客户端提供了 Redis 主从节点的地址信息。
节点发现 #
哨兵节点每隔 10s(故障转移时每隔 1s)向主从节点发送 INFO 命令,以此获取主从节点的信息。第一次哨兵仅知道 主节点 信息,通过对主节点执行 INFO 可以获取其从节点列表
INFO
命令目标是从节点:哨兵从返回信息中获取从节点所属最新主节点 ip 和 port,如果与历史记录不一致,则执行更新;获取从节点的优先级、复制偏移量以及与主节点的链接状态并更新INFO
命令目标是主节点:获取从机列表,新增则将其加入监控列表- 记录节点的 runID
- 节点的角色发生变化,哨兵会记录节点新的角色以及上报时间。
哨兵们通过发布订阅机制,通过一个约定好的通道(channel)发布、订阅 hello 信息进行通信
故障检测 #
每哥 1s,Sentinel 会通过发送 PING
命令对主从节点和其他 sentinel 节点做故障检测,确认是否可达,否则将其标记为主观下线
主观宕机
一个哨兵实例通过检测发现某个主节点发生故障的一种状态,当 PING 命令超过 down-after-millliseconds
配置的超时时间时,会将这个节点标记为主观下线
客观宕机
由于主观下线存在误判的可能性,引入客观下线的概念。哨兵检测到某个主节点发生故障,通过命令 SENTINEL is-master-down-by-addr
与其他哨兵协商,在指定时间内收到超过指定数量(quorum
)的其他哨兵的确认反馈时的一种状态。
Sentinel Leader 选举 #
当判断节点发生客观下线后,Sentinel 会选取哨兵 leader 节点进行主从故障转移。Redis 使用 Raft 算法来进行选举。有几点需要注意:
-
只有没有标记为主观下线的 sentinel 节点才有资格成为候选人。它通过向其它 sentinel 节点发送
is-master-down-by-addr
命令寻求对方将选票投给自己。 -
收到命令的 sentinel 节点会判断自身在本轮 (epoch) 的投票中自己的选票是否还存在,如果选票还在,就同意对方成为领导者,反之拒绝。
-
此时如果 sentinel 节点发现自己获得的选票达到
max(quorum, num(sentinels)/2)
,那么它将成为领导,由于每轮选举每个 sentinel 节点都只有一张票,因此只有人拿到半数以上的票数,其它人就不可能拿到半数以上的票。 -
如果本轮投票没有选出领导者,开启下一轮选举
为了达到半票以上,sentinel节点的个数至少为3个,并且为了最小化部署,通常选择sentinel节点的个数为奇数
故障迁移 #
接下来 Sentinel 会对主节点进行故障转移。它会过滤网络状态不好的节点,依次考察 优先级、复制进度、id 号
- 优先级:slave-priority 配置项,可以给从节点设置优先级。每一台从节点的服务器配置不一定是相同的,我们可以根据服务器性能配置来设置从节点的优先级。
- 复制进度,选取复制进度靠前的
- id 号,ID 号小的节点胜出
集群(Cluster)模式 #
Cluster 模式是 Redis 提供的一种去中心化的分布式方案,是一种服务器 sharding 技术,在 3.0 版本之后上线。在实现了高可用的同时,分布式的架构极大扩展了 redis 的存储、读写能力。
Cluster 模式是去中心化的分布式方案,节点之间不断进行通信来做信息交换,调整集群的状态。节点也区分主从,从节点负责处理当主节点出现故障时的自动故障转移,默认情况下从节点不支持任何的读写操作,仅作为主节点的一个“热备”存在。
对于哨兵模式,已经实现高可用,读写分离,但是每台 redis 服务器都存储相同的数据,浪费内存。而 Cluster 模式需要对数据进行分区,其引入哈希槽(hash slot),请求数据经过校验后落在对应的哈希槽中(通过 CRC16 校验后对哈希槽个数取模来决定放置在哪个槽),而每个节点会指定维护一定数量的哈希槽。
与哨兵模式区别
- 哨兵模式监控权交给了哨兵系统,集群模式中是工作节点自己做监控
- 哨兵模式发起选举是选举一个 leader 哨兵节点来处理故障转移,集群模式是在从节点中选举一个新的主节点,来处理故障的转移