前置知识: Redis

主从复制缓冲区

2 min高级

Redis 主从复制缓冲区机制:repl_backlog 环形缓冲区、全量同步与部分同步、缓冲区溢出与配置优化。

1. 复制缓冲区体系

1.1 三种复制缓冲区

缓冲区位置作用大小
repl_backlog主库存储最近写命令,支持部分同步默认 1MB
replication buffer主库为每个从库维护的输出缓冲区动态增长
replication backlog从库接收主库数据的临时缓冲动态

1.2 数据流

主库写入命令 → repl_backlog(环形缓冲)
             → replication buffer(每从库一个)
                 ↓ 网络传输
             从库接收 → 执行命令

2. repl_backlog 环形缓冲区

2.1 结构

flowchart LR
    B[repl_backlog 定长环形缓冲区<br/>[cmd1][cmd2][cmd3]...[cmdN]]
    B --> H[repl_backlog_histlen 有效数据起始]
    B --> I[repl_backlog_idx 写入位置]

总大小:repl_backlog_size(默认 1MB)。新数据写入 repl_backlog_idx 位置,写满后环绕到开头覆盖最旧数据

2.2 全局偏移量

repl_backlog_off: 缓冲区起始位置对应的全局偏移量
master_repl_offset: 主库当前的全局偏移量

有效数据范围: [repl_backlog_off, master_repl_offset]

2.3 部分同步(PSYNC)

从库断线重连时:
1. 发送 PSYNC {runid} {offset}
   - runid: 主库运行ID
   - offset: 从库最后收到的偏移量

2. 主库判断:
   - runid 匹配 且 offset 在 backlog 范围内 → 部分同步
   - 否则 → 全量同步

部分同步:
  主库从 backlog 中提取 [offset, master_repl_offset] 的数据
  发送给从库

2.4 部分同步判断

条件: offset >= repl_backlog_off

如果 offset < repl_backlog_off:
  说明从库缺失的数据已被覆盖 → 全量同步

示例:
  repl_backlog_size = 1MB
  repl_backlog_off = 1000000
  master_repl_offset = 1100000

  从库 offset = 1050000 → 1050000 >= 1000000 → 部分同步
  从库 offset = 900000  → 900000 < 1000000  → 全量同步

3. replication buffer

3.1 作用

主库为每个从库维护一个独立的输出缓冲区,暂存待发送的写命令:

flowchart TD
    T0["主库"]
    T1["replication buffer for slave1"]
    T2["replication buffer for slave2"]
    T3["replication buffer for slave3"]
    T0 --> T1
    T0 --> T2
    T0 --> T3

3.2 缓冲区溢出

当从库消费速度慢于主库写入速度时,buffer 持续增长:

写入速度: 100MB/s
从库消费: 10MB/s
每秒积压: 90MB

1分钟后: 5.4GB → 触发内存限制 → 从库被断开

3.3 缓冲区限制配置

# 客户端输出缓冲区限制(包括从库)
# 格式: client-output-buffer-limit <class> <hard> <soft> <soft_seconds>

# 从库缓冲区:硬限制 256MB,软限制 64MB 持续 60秒
client-output-buffer-limit replica 256mb 64mb 60

# 普通客户端
client-output-buffer-limit normal 0 0 0

# Pub/Sub 客户端
client-output-buffer-limit pubsub 32mb 8mb 60

触发断开条件:

  • 缓冲区超过硬限制 → 立即断开
  • 缓冲区超过软限制持续 N 秒 → 断开

4. 全量同步流程

4.1 触发条件

  • 从库首次连接主库
  • 从库发送的 runid 不匹配
  • 从库请求的 offset 已被 backlog 覆盖

4.2 全量同步步骤

1. 从库发送 PSYNC ? -1(请求全量同步)
2. 主库执行 BGSAVE 生成 RDB
3. 主库将 RDB 发送给从库
4. 主库同时将新写入命令存入 replication buffer
5. 从库接收 RDB 后清空数据并加载
6. 主库发送 replication buffer 中的增量命令
7. 从库执行增量命令,数据一致

4.3 全量同步开销

BGSAVE: fork 子进程,COW 写时复制
RDB 传输: 网络带宽
从库加载: 阻塞服务(加载期间不可用)
增量缓冲: 内存占用

5. 配置优化

5.1 repl_backlog 大小

# 根据写入速度和断线时间估算
# 假设: 写入速度 10MB/s,最大断线时间 60s
# backlog 大小 >= 10MB/s × 60s = 600MB

repl-backlog-size 600mb

计算公式:

backlog_size≥write_rate×max_disconnect_time\text{backlog\_size} \geq \text{write\_rate} \times \text{max\_disconnect\_time}

5.2 关键参数

# repl_backlog 大小
repl-backlog-size 256mb

# repl_backlog TTL(无从库时多久删除)
repl-backlog-ttl 3600

# 从库发送 PING 的频率
repl-ping-replica-period 10

# 复制超时(包括SYNC、PING)
repl-timeout 60

# 禁用 TCP_NODELAY(启用后延迟更低但带宽更大)
repl-disable-tcp-nodelay no

# 从库优先级(Sentinel 选举用)
replica-priority 100

5.3 监控命令

# 主库查看复制信息
INFO replication

# 关键指标:
# repl_backlog_active: 1
# repl_backlog_size: 268435456
# repl_backlog_first_byte_offset: 12345
# repl_backlog_histlen: 268435400
# connected_slaves: 3
# master_repl_offset: 9999999

主从配置

基本写法:REPLICAOF 设置从库 REPLICAOF <host> <port>

-- Redis 5.0+ 用 REPLICAOF(替代旧版 SLAVEOF)
REPLICAOF 192.168.1.10 6379

-- 取消从库身份,升级为主库
REPLICAOF NO ONE

-- 旧版写法(仍兼容但建议用 REPLICAOF)
SLAVEOF 192.168.1.10 6379
SLAVEOF NO ONE

基本写法:配置文件持久化 replicaof <host> <port>

# redis.conf 主从配置
replicaof 192.168.1.10 6379
masterauth <主库密码>          -- 主库有密码时设置
replica-auth-password <密码>   -- Redis 7.0+ 推荐写法

# 只读从库(默认 yes)
replica-read-only yes

# 复制缓冲区大小(应对断线重连)
repl-backlog-size 256mb
repl-backlog-ttl 3600

复制状态查询

基本写法:INFO replication INFO replication

-- 查看复制信息
INFO replication
-- 关键字段:
-- role:master|slave
-- connected_slaves: 从库数量
-- slave0:ip=...,port=...,state=online,offset=...,lag=0
-- master_repl_offset: 主库复制偏移量
-- repl_backlog_size / repl_backlog_first_byte_offset

基本写法:ROLE 查看角色 ROLE

-- 返回当前节点角色信息
ROLE
-- 主库返回:master <offset> <从库列表>
-- 从库返回:slave <主IP> <主端口> <状态> <已复制偏移量>

PSYNC 同步机制

基本写法:PSYNC 部分重同步 PSYNC <replicationid> <offset>

-- 从库内部调用,通常无需手动执行
-- 首次同步:PSYNC ? -1  触发全量 RDB 同步
-- 断线重连:PSYNC <runid> <offset>  尝试部分重同步

-- 全量同步流程:
-- 1. 从库发送 PSYNC ? -1
-- 2. 主库 BGSAVE 生成 RDB 并发送
-- 3. 主库同时缓存写命令到 backlog
-- 4. RDB 发送完,主库发送缓存的写命令
-- 5. 后续主库写命令实时复制

-- 部分重同步条件:offset 在 backlog 范围内

复制缓冲区

基本写法:调整 backlog 大小 CONFIG SET repl-backlog-size <大小>

-- backlog 越大,断线重连时部分重同步成功率越高
CONFIG SET repl-backlog-size 512mb

-- 主库无从库时 backlog 释放时间(秒),0=永不释放
CONFIG SET repl-backlog-ttl 3600

-- 客户端输出缓冲区(从库连接)
CONFIG SET client-output-buffer-limit 'replica 256mb 64mb 60'

级联复制

基本写法:链式复制 REPLICAOF <中间从库> <port>

-- 一主多从的级联结构,减轻主库压力
-- master <- slave1 <- slave2
-- slave2 指向 slave1
REPLICAOF 192.168.1.11 6379   -- slave1 的地址

-- slave1 配置允许级联复制(默认允许)
-- replica-serve-stale-data yes

读写分离与一致性

基本写法:WAIT 等待复制 WAIT <numreplicas> <timeout毫秒>

-- 等待 N 个从库确认写入,返回确认的从库数
SET key1 value1
WAIT 1 1000    -- 等待 1 个从库确认,最多等 1000ms

-- 注意:WAIT 只等待当前命令的复制,不保证持久化
-- 不影响后续命令,仅返回已确认从库数量

基本写法:只读从库配置 CONFIG SET replica-read-only yes

-- 从库默认只读,禁止写入
CONFIG SET replica-read-only yes

-- 主从延迟导致读到旧数据的应对:
-- 1. 关键读走主库
-- 2. 使用 WAIT 等待复制
-- 3. 读从库后校验 offset

复制故障排查

基本写法:排查复制中断 INFO replication | LATENCY

-- 1. 检查从库连接状态
INFO replication
-- state 不为 online 时检查网络/密码

-- 2. 检查 master_link_status
-- master_link_status:down 表示连接断开

-- 3. 检查复制偏移量差距
-- master_repl_offset - slave_repl_offset = 滞后字节数

-- 4. 查看日志
LOG GET 100

-- 5. 强制全量重同步
REPLICAOF NO ONE
REPLICAOF <master_ip> <master_port>

复制过滤

基本写法:选择性复制 replica-serve-stale-data | replica-priority

# redis.conf 配置
# 从库与主库断开后是否继续提供旧数据服务
replica-serve-stale-data yes

# 从库优先级(哨兵选主用,0=永不被选为主)
replica-priority 100

# 忽略某些 key 的复制(Redis 7.0+ 已移除,改用 ACL)
# 旧版:replica-ignore-maxmemory no