建立复制
参与复制的 Redis 实例被划分为:
- 主节点
master
- 从节点
slave
一个主节点可以有多个从节点,数据的复制是单向的 master -> slave
。
通过主从复制,可以实现「读写分离」,主服务器负责「写」操作,从服务器负责「读」操作。
同时,从节点也可以作为主节点的备份。
在从服务器中,使用 slaveof
/ replicaof
命令/配置,建立主从服务关系,执行命名后,从节点只会保存主节点的信息,其余的复制流程在节点内部异步进行。
|
|
同步原理
Redis 内部使用 psync
完成主从数据的同步。
|
|
👣 主服务器会执行
bgsave
命令来生成 RDB 文件,然后把文件发送给从服务器。👣 从服务器收到 RDB 文件后,会先「清空当前的数据」,然后载入 RDB 文件。
👣 在复制期间,主服务器依然可以正常处理命令(通过fork子进程,使用 copy-on-wirite COW),为了保障数据的一致性,写命令会被同时记录在 「复制客户端缓冲区」
replication buffer
中,从服务器加载完 RDB 后,会继续运行缓存区中记录的命令。
部分复制
📃 复制偏移量 offset
参与主从的节点都会维护自身的复制偏移量,处理完「写命令」后,都会累计命令的字节长度。
从节点会每秒钟会向主节点上报自身的复制偏移量。
♒ 复制积压缓冲区
replication backlog buffer
本质上一个先进先出的定长队列(默认两个,2 * 1M),与 MySQL redolog 一样是循环写入,新数据会覆盖旧数据。
主节点响应写命令后,发送命令到从节点时,同时会将数据写入复制积压缓冲区,用于部分复制命令丢失是的数据补救。
主从服务器在完成第一次全量复制之后,会通过长连接进行命令传播。
由于网络是不稳定的,从服务器可能会有短暂的断开,当重新建立连接之后,可以根据情况使用增量复制来同步数据,避免了全量复制带来的性能开销。
增量复制同样采用了 psync
来同步,从服务器通过 offset 来告知主服务器当前的数据偏移情况。
当从服务器的偏移量能够在复制积压缓冲区中找到,会进行部分复制;当偏移数据过大,已被覆盖无法找到时,会退化为全量复制。
💓 保持连接
主从节点间有心跳检测机制:
主节点每 10s 向从节点发送
ping
命令,判断从节点的连接状态从节点每 1s 上报自身 offset 情况
- 实时监控从节点网络状态,延迟性
- 判断数据是否丢失
- 保障从节点的连接数量
📚 读写分离问题
🎰 数据延迟
Redis 由于异步复制的特性,延迟是无法避免的,取决于当前的网络情况和命令阻塞情况。
适合于业务场景对延迟不敏感的场景。根据具体业务场景而定,可以考虑使用 Redis 集群做水平扩展的方案。
🗑️ 过期数据
在 Redis 中,有(1)定期删除;(2)惰性删除;两种删除方案。
在从服务器中,不会主动检查是否过期,主服务器中找到过期数据,会同步一条 DEL
命令到从服务器中,删除过期数据。
在 Redis 3.2-,从服务器会直接返回过期数据,不会进行检查
在 Redis 3.2+,在返回数据前,会检查过期时间,解决了从服务器读取到过期数据的问题
🌀 节点故障
当主节点宕机后,从节点是无法自动升级为主节点的,需要人工干预。
考虑使用 Redis 哨兵模式或集群方案。
👍 相关优化
避免全量复制
主从复制的全量复制是一个非常消耗资源的操作,除了第一次复制的全量复制,之后需要尽量避免全量复制。
复制积压缓存区不足,会导致复制退化为全量复制,在运维时,可以根据实际情况增加缓冲区的容量。
避免复制风暴
当一个主节点上挂载了多个从节点,如果同时与多个从节点进行全量同步时,会向多个从节点发送RDB文件(Redis 做了优化可共享一个 RDB),导致网络带宽的严重消耗,造成延迟问题。
建议使用链状/树状结构,从节点挂载到从节点上。
单机复制风暴
由于 Redis 的瓶颈不在 CPU 上,通常会在一台服务器上部署多个实例。 为了避免物理机问题导致的故障,导致恢复后大量从节点向一台机器进行复制操作,部署时应该把不同服务的主节点分散到多台机器上。
🎺 哨兵机制
Redis 在主从模式下,一旦主节点故障😓,需要人工干预将从节点提升为主节点,同时需要在客户端配置新的主节点地址。
为了达到高可用,提供了哨兵模式 Redis Sentinel
解决这个问题,自动完成故障发现和故障转移。
在哨兵模式下,相比于复制模式,只是多了 Sentinel 节点,主从模式下的 Redis 节点没有任何特殊处理。 其中,Sentinel 节点本质上是特殊的 Redis 节点,只是不会存储数据,只支持部分命令。
🔍 监控
每个哨兵节点会定期检查 Redis 数据节点、其余哨兵节点是否可达。
📧 通知
哨兵节点会将故障转移的结果通知给应用方。
📇 主节点故障转移
可以实现故障时,将从节点晋升为主节点,并维护正确的主从关系。
📑 配置提供者
在哨兵结构中,客户端初始化连接的是哨兵集群,从中获取主节点的信息。
主观下线和客观下线
🧐 主观下线
”一家之言“
Sentinel 每 1s 会向其他所有节点发送 ping
进行💓心跳检测,当节点超过时间没有回复,就会对该节点做失败判定。只有这一个节点的判定称为「主观下线」。
🗳️ 客观下线
当主观下线的为主节点时,当前 Sentinel 节点会询问其他哨兵对该主节点的判断,当多数哨兵都对主节点的下线做了同意判定,超过配置中的 quorum 个数,该节点就被标记为「客观下线」。
三个定时任务
Redis Sentinel 通过三个定时任务对各个节点发现和监控:
每隔 10s,每个哨兵节点会向主节点和从节点发送 info 命令获取最新的拓扑结构
每隔 2s,每个哨兵节点会向 Redis 数据节点的 __sentinel__:hello 频道上发送该 Sentinel 节点对于主节点的判断,以及当前 Sentinel 节点的信息
每隔 1s,每个哨兵会向其他哨兵节点、主节点、从节点发送 ping 命令判断是否可达
Sentinel 领导选举
当确定客观下线之后,需要进行故障转移工作。实际上故障转移工作只需要一个 Sentinel 节点来完成即可,所以需要先做一个领导者选举的工作。
Redis 使用 Raft 算法实现领导的选举。
每个在线的 Redis Sentinel 都有资格成为领导者,在一个哨兵确定主节点客观下线时,会向其他的节点发送 sentinel is-master-down-by-addr
命令,
要求将自己设置为领导者。
收到命令的节点,如果没有同意过其他节点的请求,就会同意该请求,否则拒绝。每个哨兵节点只有一票。
当一个哨兵拿到 quorum 个票数时,就成为了领导者,否则进行下一次投票。
🎡 故障转移
👣 选取新的主节点
🧩 过滤“不健康”节点(主观下线、掉线)
🧩 选择「优先级最高」的节点,否则继续下一条规则
🧩 选取「offset 最大」的节点,否则下一条规则
🧩 选取「runId 最小」的节点(runId 是 Redis 随机生成的一个id,在集群中这个 id 会保持不变)
👣 从节点提升为主节点
从节点内部执行
slaveof no one
成为主节点。👣 哨兵领导者向其他从节点发送命令,更换主节点的信息
👣 将下线的主节点更新为从节点,并保持监控,当再次上线时,将作为从节点复制新的主节点信息
注意事项
- Sentinel 节点应该部署在多台物理机上
Redis 的瓶颈不在 CPU,通过一台物理机会部署多个 Redis 实例,如果哨兵全部都在一台物理机上,如果出现物理故障,所有的实例都会收到影响
- 至少三个 Sentinel 节点,且为奇数节点
哨兵领导者选举时,至少需要获取「一半 + 1」个投票。奇数节点可以在满足条件的基础上节省一个节点。
参考
- https://segmentfault.com/a/1190000039766545
- Redis 开发于运维