Featured image of post Redis 复制

Redis 复制

建立复制

参与复制的 Redis 实例被划分为:

  • 主节点 master
  • 从节点 slave

一个主节点可以有多个从节点,数据的复制是单向的 master -> slave

通过主从复制,可以实现「读写分离」,主服务器负责「写」操作,从服务器负责「读」操作。

同时,从节点也可以作为主节点的备份。

在从服务器中,使用 slaveof / replicaof 命令/配置,建立主从服务关系,执行命名后,从节点只会保存主节点的信息,其余的复制流程在节点内部异步进行。

1
2
3
4
5
6
7
8
# 配置文件中设置
slaveof {masterHost} {masterPort}

# 启动从服务时
redis-server --slaveof {masterHost} {masterPort}

# 从服务中使用命令
slaveof {masterHost} {masterPort}

同步原理

Redis 内部使用 psync 完成主从数据的同步。

1
2
3
# runID 保存的主服务器的 id
# offset 偏移量,第一次同步为 -1
psync {runID} {offset}
  • 👣 主服务器会执行 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」个投票。奇数节点可以在满足条件的基础上节省一个节点。

参考

Licensed under CC BY-NC-SA 4.0