Skip to content

主从复制:从一条命令到复制断裂排查 ​

主从是哨兵和 Cluster 的地基。这一节先把单 master 多 replica 跑通,并把"为什么从库读不到刚写的数据"这类问题讲透。

场景:什么时候要主从 ​

  • 读多写少,想把读流量分出去。
  • 需要一份实时热备,master 挂了能顶上(这就要接着上哨兵)。
  • 做备份时不想在 master 上 BGSAVE。
  • 为 Cluster 做前置实验。

一、最小拓扑 ​

角色IP端口
master10.0.0.116379
replica-110.0.0.126379
replica-210.0.0.136379

三台都按 源码安装 装好,master 设 requirepass。

二、master 侧配置 ​

ini
# /etc/redis/6379.conf (master)
bind 0.0.0.0
protected-mode no
requirepass YourStrongPass

# 复制相关
repl-backlog-size 64mb
repl-diskless-sync yes
repl-diskless-sync-delay 5

# 防止脑裂:至少 1 个从库在线且延迟 < 10s 才允许写
min-replicas-to-write 1
min-replicas-max-lag 10

解释:

  • repl-backlog-size 是复制积压缓冲区。从库短暂断线重连时,靠它做"部分重同步",不用全量传 RDB。默认 1mb,写 QPS 高时几秒就填满,一断线就退化成全量复制。生产给 64~256mb。
  • repl-diskless-sync yes 让 master 直接把 RDB 通过 socket 推给从库,不落磁盘。容器/云盘环境更合适。
  • repl-diskless-sync-delay 5 是等 5 秒攒一批从库一起传,减少多次 fork。只连一台从库时改成 0。
  • min-replicas-to-write 是防脑裂的:网络分区后旧 master 收不到从库确认,就拒绝写入,避免出现两份可写数据。

三、replica 侧配置 ​

ini
# /etc/redis/6379.conf (replica)
bind 0.0.0.0
protected-mode no
requirepass YourStrongPass          # 从库自身的密码,客户端连它时用
masterauth YourStrongPass           # 连 master 时用的密码,别漏

replicaof 10.0.0.11 6379
replica-read-only yes
replica-serve-stale-data yes
replica-priority 100

# 从库建议不落 AOF,避免重启加载本地旧数据
appendonly no

两处密码别写反:

配置谁校验写错的表现
requirepass客户端连本实例时客户端 NOAUTH
masterauth本实例连 master 时info replication 里 master_link_status:down,日志 Unable to AUTH to MASTER
bash
systemctl restart redis-6379
tail -20 /var/log/redis/6379.log

从库日志(首次全量同步):

text
* Connecting to MASTER 10.0.0.11:6379
* MASTER <-> REPLICA sync started
* Non blocking connect for SYNC fired the event.
* Master replied to PING, replication can continue...
* Trying a partial resynchronization (request 8f2c1e9a...:1).
* Full resync from master: 3d41b7c0e5a29f6b8c1d0e2f4a6b8c9d0e1f2a3b:0
* MASTER <-> REPLICA sync: receiving 21MB from master
* MASTER <-> REPLICA sync: Flushing old data
* MASTER <-> REPLICA sync: Loading DB in memory
* MASTER <-> REPLICA sync: Finished with success

解释:

  • 第一次连一定走 Full resync:master fork 出 RDB 传过来。
  • 之后再断线,会先尝试 partial resynchronization(PSYNC <replid> <offset>),成功就是增量。

四、验证复制状态 ​

master 上:

bash
redis-cli -a '密码' info replication
text
# Replication
role:master
connected_slaves:2
slave0:ip=10.0.0.12,port=6379,state=online,offset=184320,lag=0
slave1:ip=10.0.0.13,port=6379,state=online,offset=184320,lag=0
master_failover_state:no-failover
master_replid:3d41b7c0e5a29f6b8c1d0e2f4a6b8c9d0e1f2a3b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:184320
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:67108864
repl_backlog_first_byte_offset:1
repl_backlog_histlen:184320

replica 上:

bash
redis-cli -a '密码' info replication
text
# Replication
role:slave
master_host:10.0.0.11
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_read_repl_offset:184320
slave_repl_offset:184320
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:3d41b7c0e5a29f6b8c1d0e2f4a6b8c9d0e1f2a3b
master_repl_offset:184320
repl_backlog_active:0

字段含义(只需要盯这四个):

字段正常值异常说明
master_link_statusupdown = 复制断了,看日志
master_last_io_seconds_ago0~1持续 >10 = 网络或 master 卡住
master_sync_in_progress0长期 1 = 正在全量同步,传输太慢
slave_repl_offset与 master 的 master_repl_offset 接近差值就是积压的写命令数

写入验证:

bash
redis-cli -a '密码' -h 10.0.0.11 set repl:test 1
redis-cli -a '密码' -h 10.0.0.12 get repl:test
redis-cli -a '密码' -h 10.0.0.12 set repl:test 2

输出:

text
OK
"1"
(error) READONLY You can't write against a read only replica.

五、复制是异步的 ​

这是最常被误解的一点:set 返回 OK 只代表 master 写进内存了,从库可能还没收到。

bash
redis-cli -a '密码' -h 10.0.0.11 set order:1 paid
redis-cli -a '密码' -h 10.0.0.12 get order:1     # 可能返回 (nil)

如果业务真的需要"至少 N 个从库确认",用 WAIT:

bash
redis-cli -a '密码' set order:1 paid
redis-cli -a '密码' wait 1 100        # 等 1 个从库确认,最多 100ms

输出:

text
OK
(integer) 1

返回 1 表示有 1 个从库确认了;返回 0 表示超时(不代表写失败,只是没等到确认)。

WAIT 不是强一致

WAIT 只保证"命令已到达从库内存",不保证从库持久化,也不保证切换后数据还在。要真正的强一致得用 WAIT + AOF always + 哨兵/Cluster 的正确选主策略,代价很大。绝大多数业务应该接受异步复制,靠"读自己的写"(会话粘性)来规避。

六、只读从库读不到刚写的数据 ​

这是异步复制的直接后果,现象是"用户刚保存,刷新页面还是旧值"。三种解法:

  1. 读写都走 master(最简单,读扩展就没了)。
  2. 写后短时间内读 master:在会话里记一个时间戳,N 秒内的读强制走主库。
  3. 按 key 路由:刚写过的 key 加白名单,命中就读主库。

别做的事:给从库加 replica-read-only no 让业务随便写。多写节点一定会分叉,之后没有任何自动化手段能合并。

七、复制断裂排查顺序 ​

按这个顺序走,基本三步内定位:

1. 看日志 ​

bash
grep -Ei 'master|sync|repl' /var/log/redis/6379.log | tail -30

典型报错与含义:

日志含义
Unable to AUTH to MASTERmasterauth 错或 master 改了密码
MASTER timeout: no data nor PING received网络不通 / 防火墙 / master 假死
Connection with master lost连接被断,随后会尝试重连
Unable to partial resync with the masterbacklog 不够,退化全量
Diskless rdb transfer failedmaster 的磁盘或 socket 发送异常
Replica is in an invalid state手工改过复制关系

2. 看网络与端口 ​

bash
# 在从库上
timeout 3 bash -c 'cat < /dev/null > /dev/tcp/10.0.0.11/6379' && echo tcp-ok
redis-cli -h 10.0.0.11 -p 6379 -a '密码' ping
ss -lntp | grep 6379

如果 redis-cli 能 PING 通但复制仍 down,多半是密码或 replicaof 配错,不是网络。

3. 看是否在反复全量同步 ​

bash
# 从库
redis-cli -a '密码' info stats | grep -E 'sync_full|sync_partial'
text
sync_full:17
sync_partial_ok:0
sync_partial_err:0

sync_full 一直在涨、sync_partial_ok 是 0,说明每次断线都在全量重传。两个原因:

  • repl-backlog-size 太小,断线时间一长 offset 就滑出缓冲区。
  • master 重启了:master_replid 变了,旧 replid 保留在 master_replid2 里,只有 offset 还在范围内才能续上。
bash
redis-cli -a '密码' config set repl-backlog-size 128mb
redis-cli -a '密码' config rewrite

改 backlog 不用重启

repl-backlog-size 支持 config set 热改,改完对后续的复制生效,已在复制的从库不会立刻扩容。

八、手工切换与重建 ​

把某台从库提升为主 ​

bash
redis-cli -a '密码' -h 10.0.0.12 replicaof no one
redis-cli -a '密码' -h 10.0.0.12 info replication | head -3
text
OK
role:master
connected_slaves:0

其他从库挂到新主上:

bash
redis-cli -a '密码' -h 10.0.0.13 replicaof 10.0.0.12 6379
redis-cli -a '密码' -h 10.0.0.11 replicaof 10.0.0.12 6379

旧 master 恢复后必须先转从

旧 master 重新上线时它仍然认为自己是 master,如果直接放回流量就是双写。动手前先停机、改配置成 replicaof 新主 6379,再启动。

另一个风险:旧 master 上有一批新主没有的写(脑裂期间),转从时全量同步会清空这些数据。要么接受丢失,要么手工导出比对。

重建一个坏掉的从库 ​

bash
systemctl stop redis-6379
rm -rf /var/lib/redis/6379/dump.rdb /var/lib/redis/6379/appendonlydir
systemctl start redis-6379
redis-cli -a '密码' info replication | grep -E 'role|link_status'

全量同步会重新灌一遍。数据量大时优先用"拷贝 RDB + 开复制"的方式减少 master 压力:

bash
# 在 master 上
redis-cli -a '密码' bgsave
scp /var/lib/redis/6379/dump.rdb 10.0.0.14:/var/lib/redis/6379/
# 从库启动后再 replicaof

九、踩过的坑 ​

现象根因处置
master_link_status:down,日志 Unable to AUTHmasterauth 密码错对齐 master 的 requirepass
从库可写replica-read-only no改回 yes
每次重启都全量同步从库开了 AOF 且加载了旧数据,offset 对不上从库 appendonly no,或重建
复制延迟持续增长从库单线程执行慢命令(大 key DEL、KEYS)用 UNLINK,禁 KEYS
master 内存翻倍从库断线重连触发全量,fork + COW加大 backlog,减少断线
主从数据不一致但复制正常有人在从库上直接写了查 replica-read-only,重建从库
切换后业务报 READONLY客户端还连着旧 master用哨兵/Cluster 客户端,或更新配置
从库 master_link_status:up 但数据旧只同步到一半(master_sync_in_progress:1)等,或查网络带宽