Redis 第六章:主从复制的使用和原理
在分布式系统中,单个节点往往会带来两个问题:一是可用性不足,节点宕机后服务就会中断;二是性能有限,所有读写请求都压在同一台机器上,压力很容易集中。Redis 的主从复制就是为了解决这类问题而设计的:它把主节点的数据复制到一个或多个从节点上,让 Redis 拥有多个数据副本。
主从复制是 Redis 高可用体系的基础。后续的哨兵机制和集群机制,都建立在复制能力之上。可以把主节点理解成"授课老师",从节点理解成"助教":老师负责产生知识,助教从老师那里同步知识,再帮助处理更多同学的问题。
6.1 配置
Redis 参与复制的实例分为主节点和从节点。每个从节点只能有一个主节点,而一个主节点可以同时拥有多个从节点。复制的数据流是单向的,只能从主节点流向从节点。
建立复制关系常见有三种方式。
第一种是在配置文件中写入:
bash
slaveof {masterHost} {masterPort}
这种方式会随着 Redis 启动生效。
第二种是在启动 redis-server 时加上参数:
bash
redis-server redis-slave.conf --port 6380 --slaveof 127.0.0.1 6379
第三种是在 Redis 运行过程中直接执行命令:
bash
slaveof {masterHost} {masterPort}
通常情况下,主节点不需要额外修改配置,主要是在从节点上指定它要复制哪个主节点。例如主节点运行在 6379 端口,从节点运行在 6380 端口,可以让 6380 作为 6379 的从节点。配置完成后,在主节点写入数据:
bash
127.0.0.1:6379> set hello world
OK
再到从节点读取:
bash
127.0.0.1:6380> get hello
"world"
如果从节点能读到主节点写入的数据,就说明复制已经生效。
还可以通过 info replication 查看复制状态。主节点会显示 role:master,并能看到已连接的从节点数量;从节点会显示 role:slave,并能看到主节点地址、端口、连接状态、复制偏移量等信息。
除了建立复制,slaveof 还可以断开复制关系。在从节点执行:
bash
slaveof no one
从节点会断开与主节点的复制关系,并晋升为主节点。需要注意的是,断开复制不会删除从节点原有的数据,只是它之后无法再继续获取原主节点上的数据变化。
slaveof 也可以用来切换主节点:
bash
slaveof {newMasterIp} {newMasterPort}
切主的大致流程是:先断开旧主节点,再连接新主节点,然后清空当前从节点的数据,最后从新主节点重新复制数据。
在安全性方面,如果主节点配置了 requirepass 密码验证,那么从节点也必须配置 masterauth,保证从节点能通过主节点的认证,否则复制无法正常建立。
默认情况下,从节点是只读的,也就是 slave-read-only=yes。这是合理的,因为 Redis 复制只能从主节点同步到从节点。如果直接修改从节点,主节点并不会感知这些变化,最终会导致主从数据不一致。因此线上环境一般不建议关闭从节点只读模式。
复制还需要考虑网络传输延迟。Redis 提供了 repl-disable-tcp-nodelay 参数来控制是否关闭 TCP_NODELAY。默认值为 no,表示开启 TCP_NODELAY,主节点会尽快把数据发送给从节点,延迟更低,但带宽消耗会更高。如果设置为 yes,则会合并较小的数据包,节省带宽,但主从延迟会变大。简单来说,同机房、网络条件好的环境更适合低延迟;跨机房、网络复杂的环境可能更关注带宽。
6.2 拓扑
Redis 主从复制支持多种拓扑结构,课件中主要介绍了三类:一主一从、一主多从、树状主从结构。
一主一从是最简单的复制结构。一个主节点对应一个从节点,从节点可以作为主节点的数据备份。当应用写入并发比较高,同时又需要持久化时,可以考虑只在从节点开启 AOF。这样既能保证一定的数据安全,又能减少持久化操作对主节点性能的影响。不过,如果主节点关闭了持久化功能,主节点宕机后要避免直接自动重启,否则可能出现数据被空数据集覆盖的风险。
一主多从也叫星形结构。一个主节点连接多个从节点,应用可以把写请求发给主节点,把读请求分散到多个从节点上,从而实现读写分离。对于读多写少的场景,这种结构可以有效分担主节点的读压力。一些比较耗时的读操作,也可以指定到专门的从节点上执行,避免影响主业务链路。但是,如果写并发很高,主节点需要把写命令同步给多个从节点,从节点越多,主节点的复制压力也越大。
树状主从结构也叫分层结构。从节点不仅可以复制主节点的数据,还可以继续作为其他从节点的"上游"。例如数据先从 A 同步到 B 和 C,再由 B 同步给 D 和 E。这样可以引入复制中间层,减少主节点直接挂载大量从节点时的网络和复制压力。当从节点数量较多时,树状结构会比单纯的一主多从更适合。
6.3 原理
主从复制不是简单地"把数据拷贝过去",它包含一套完整流程。课件中把建立复制的过程分为六步。
第一步,从节点保存主节点信息。当执行 slaveof 127.0.0.1 6379 后,从节点会先记录主节点的 IP 和端口,但此时连接可能还没真正建立。
第二步,从节点尝试和主节点建立 TCP 连接。Redis 内部会通过定时任务维护复制逻辑,如果连接失败,会持续重试,直到连接成功或用户主动停止复制。
第三步,从节点发送 ping 命令。TCP 连接成功不代表 Redis 应用层一定可用,所以从节点会通过 ping 确认主节点能正常响应。如果没有及时收到 pong,从节点会断开连接并等待下一次重连。
第四步,进行权限验证。如果主节点配置了 requirepass,从节点必须使用 masterauth 提供正确密码。认证失败,复制流程就会停止。
第五步,同步数据集。首次建立复制时,主节点会把当前持有的数据发送给从节点。这是复制过程中最重的一步,后面又可以细分为全量复制和部分复制。
第六步,命令持续复制。当从节点完成初始数据同步后,主节点之后收到的写命令会继续发送给从节点,从节点执行这些命令,从而保持主从数据一致。
Redis 使用 psync 命令完成主从数据同步。它有两个关键参数:replicationid 和 offset。
replicationid 可以理解为复制 ID,用来标识主节点的数据历史。主节点重启,或者从节点晋升成主节点时,都可能生成新的复制 ID。Redis 中还会维护 master_replid 和 master_replid2 两组 ID,用来处理主从切换、网络恢复等场景。
offset 是复制偏移量,可以理解为同步进度。主节点每处理一条写命令,都会根据命令长度累加自己的复制偏移量;从节点接收到命令并执行后,也会累加自己的偏移量。通过比较主从节点的偏移量,就可以判断它们的数据同步进度是否一致。
replicationid + offset 共同标识一个数据集。如果两个节点的复制 ID 和偏移量都相同,就可以认为它们持有的数据是一致的。
psync 的运行结果主要有三种。
如果主节点回复:
text
+FULLRESYNC replid offset
说明从节点需要进行全量复制。
如果主节点回复:
text
+CONTINUE
说明可以进行部分复制。
如果主节点回复:
text
-ERR
说明主节点版本过低,不支持 psync,从节点只能退回到早期的 sync 全量复制方式。实际使用中,psync 通常不需要手动执行,Redis 会在主从复制过程中自动调用。
全量复制通常发生在第一次建立主从复制时。由于从节点没有主节点的复制 ID 和偏移量,所以会发送类似 psync ? -1 的请求。主节点判断后返回 FULLRESYNC,然后执行 bgsave 生成 RDB 文件,并把 RDB 数据发送给从节点。从节点接收并加载 RDB 文件,在这期间主节点产生的新写命令会进入缓冲区,等从节点加载完成后再补发过去,保证最终一致。
全量复制的成本很高:主节点要执行 bgsave,RDB 文件要通过网络传输,从节点要清空旧数据并加载 RDB。如果从节点开启了 AOF,还可能在加载完成后执行 AOF 重写。因此,对于数据量较大的 Redis 实例,应尽量避免频繁触发全量复制。
Redis 从 2.8.18 开始支持无磁盘复制。默认全量复制会先在主节点磁盘上生成 RDB 文件,再把文件发送给从节点;无磁盘复制则是在生成 RDB 数据时直接通过网络发送给从节点,减少磁盘读写开销。
部分复制是为了减少全量复制成本而设计的。典型场景是主从之间出现短暂网络闪断,连接恢复后,从节点不必重新复制全部数据,而是把自己保存的 replicationid 和 offset 发给主节点,请求补齐中间丢失的那一段命令。如果主节点的复制积压缓冲区里还保存着这段数据,就可以直接补发,从而完成部分复制。
复制积压缓冲区保存在主节点上,是一个固定长度的先进先出队列,默认大小为 1MB。当主节点存在从节点时,主节点处理写命令时不仅会把命令发送给从节点,也会把命令写入复制积压缓冲区。它的作用就是保存最近一段已复制的数据,用于网络恢复后的数据补救。
可以通过 info replication 查看复制积压缓冲区相关信息,例如:
text
repl_backlog_active
repl_backlog_size
repl_backlog_first_byte_offset
repl_backlog_histlen
主节点能提供的部分复制范围,大致由 repl_backlog_first_byte_offset 和 repl_backlog_histlen 决定。如果从节点需要补的数据已经超出了缓冲区范围,就无法进行部分复制,只能重新进行全量复制。
除了全量复制和部分复制,主从之间还存在实时复制。主从连接建立之后,主节点会通过 TCP 长连接,把后续收到的写命令持续发送给从节点。从节点收到命令后执行同样的修改,从而保持数据同步。
这条长连接还需要心跳机制维护。主节点默认每隔 10 秒向从节点发送 ping,判断从节点是否存活;从节点默认每隔 1 秒向主节点发送 replconf ack {offset},上报自己的复制偏移量。如果主节点发现从节点通信延迟超过 repl-timeout,默认 60 秒,就会认为从节点下线并断开复制连接。等从节点恢复后,再重新进入复制流程。
6.4 本章重点回顾
主从复制主要解决 Redis 单点问题。一方面,单个 Redis 节点可用性不高,宕机后服务会受到影响;另一方面,单个节点性能有限,读写压力都集中在一起。通过复制,Redis 可以拥有主节点的多个副本,让主节点负责写,从节点承担读,从而降低主节点访问压力。
主从复制支持多种拓扑结构:一主一从适合简单备份和故障准备;一主多从适合读多写少、需要读写分离的场景;树状主从适合从节点数量较多、需要降低主节点复制压力的场景。
从原理上看,复制可以分为全量复制、部分复制和实时复制。全量复制适合首次同步,但成本较高;部分复制适合短暂断线后的数据补齐,依赖复制积压缓冲区;实时复制则负责在主从连接稳定后持续同步新的写命令。
配置主从复制时,主节点通常不需要改动,主要是在从节点配置:
bash
slaveof 主节点ip 主节点端口
不过,主从复制也有明显缺点。第一,从节点越多,复制延迟和主节点同步压力越明显;第二,主节点宕机后,从节点不会自动升级为主节点,需要人工干预。这也是下一章 Redis 哨兵机制要解决的问题。