Redis主从复制原理

Redis主从复制原理

Redis 的五种数据结构各有特色,用对了才能发挥它的优势。很多人只用到了 String 和 Hash,却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发,讲什么时候用什么类型。

一、复制架构

#

1.1 基本架构

┌─────────┐      复制      ┌─────────┐
│ Master │ ──────────> │ Slave │
│ (读写) │ │ (只读) │
└─────────┘ └─────────┘

#

1.2 一主多从

         ┌──────────> ┌─────────┐
┌────────┐ │ Slave 1 │
│ Master │ ──────────>├─────────┤
└────────┘ │ Slave 2 │
└──────────> │ Slave 3 │
└─────────┘

#

1.3 级联复制

Master ──> Slave1 ──> Slave2

└────> Slave3

二、复制流程

#

2.1 全量同步(Full Resynchronization)

Slave                    Master
│ │
│ 1. 连接Master │
│ ──────────────────────> │
│ │
│ 2. 发送PSYNC命令 │
│ ──────────────────────> │
│ │
│ 3. 返回+FULLRESYNC │
│ <────────────────────── │
│ runid │
│ offset │
│ │
│ 4. 发送RDB文件 │
│ <────────────────────── │
│ │
│ 5. 加载RDB │
│ │
│ 6. 接收后续写命令 │
│ <────────────────────── │
│ (复制积压缓冲区) │

详细步骤

  1. Slave连接Master

    # Slave配置
    replicaof 192.168.1.100 6379
  2. 发送PSYNC命令

    PSYNC <runid> <offset>

    首次复制:
    PSYNC ? -1

    断线重连:
    PSYNC <master_runid> <slave_offset>
  3. Master响应

  • 如果runid匹配且offset在复制积压缓冲区内:返回+CONTINUE(部分重同步)
  • 否则:返回+FULLRESYNC <runid> <offset>(全量同步)
  1. Master生成并发送RDB

    Master执行BGSAVE → 生成RDB文件 → 发送给Slave
  2. Slave加载RDB

    Slave清空内存 → 加载RDB文件 → 数据与Master一致
  3. 同步后续写命令

    Master将RDB生成后的写命令写入复制积压缓冲区
    Slave接收并执行这些命令

#

2.2 增量同步(Partial Resynchronization)

Slave                    Master
│ │
│ 网络断开 │
│ XXXXXXXXXXXXXXXXXXXXX │
│ │
│ 网络恢复 │
│ │
│ 发送PSYNC runid offset │
│ ──────────────────────> │
│ │
│ 3. 返回+CONTINUE │
│ <────────────────────── │
│ │
│ 4. 发送缺失的命令 │
│ <────────────────────── │

触发条件

  • Slave之前同步过该Master(runid匹配)
  • Slave的offset在Master的复制积压缓冲区内

三、核心机制

#

3.1 复制积压缓冲区(Replication Backlog)

Master维护一个固定大小的FIFO队列:

┌─────────────────────────────────────────────────────────────┐
│ 复制积压缓冲区(默认1MB) │
│ │
│ offset:1000 offset:1001 offset:1002 offset:1200 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ SET a 1 │ │ SET b 2 │ │ DEL c │ ... │ SET x 9 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ master_repl_offset = 1200 │
└─────────────────────────────────────────────────────────────┘

Slave断线后重连,offset=1001:
- 1001在缓冲区内(1000~1200)
- 只需发送1001~1200的命令

Slave断线后重连,offset=500:
- 500不在缓冲区内(已超出)
- 触发全量同步

配置

# 复制积压缓冲区大小(建议根据写入量调整)
repl-backlog-size 10mb

# Slave断线后,Master保持缓冲区的超时时间
repl-backlog-ttl 3600

#

3.2 PSYNC机制

Redis 2.8之前使用SYNC,只能全量同步。
Redis 2.8+ 使用PSYNC,支持增量同步。

// PSYNC命令处理
void syncCommand(client *c) {
// 检查是否是PSYNC
if (!strcasecmp(c->argv[0]->ptr, "psync")) {
// 尝试部分重同步
if (masterTryPartialResynchronization(c) == C_OK) {
// 增量同步成功
return;
}
// 失败,进行全量同步
}

// 全量同步流程
// 1. 生成RDB
// 2. 发送RDB
// 3. 发送后续命令
}

#

3.3 Master的RunID

RunID是40字符的随机十六进制字符串:
- Master启动时生成
- 用于标识Master实例
- Slave记录Master的RunID

作用:
- 判断Slave是否之前同步过该Master
- 如果RunID不同,说明Master重启过,必须全量同步

#

3.4 传播写命令

Master执行写命令:

1. 修改内存数据
2. 写入AOF(如果开启)
3. 传播给所有Slave:

对于每个Slave:
- 写入Slave的输出缓冲区
- 等待Slave的ACK(如果配置了min-slaves)

四、配置

#

4.1 Slave配置

# redis.conf (Slave)

# 方式一:配置文件中指定Master
replicaof 192.168.1.100 6379

# 方式二:启动参数
# redis-server --replicaof 192.168.1.100 6379

# 方式三:运行时配置
# redis-cli REPLICAOF 192.168.1.100 6379

# 如果Master有密码
masterauth password

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

# 复制优先级(Sentinel使用,数值小的优先提升为Master)
replica-priority 100

#

4.2 Master配置

# redis.conf (Master)

# 允许Slave数量(用于延迟计算)
min-replicas-to-write 1
min-replicas-max-lag 10

# 复制积压缓冲区
repl-backlog-size 10mb
repl-backlog-ttl 3600

# 禁用TCP_NODELAY(减少网络小包)
repl-disable-tcp-nodelay no

#

4.3 无磁盘复制(Diskless Replication)

Redis 2.8.18+ 支持直接通过网络发送RDB:

# Master直接通过网络发送RDB,不写入磁盘
repl-diskless-sync yes

# 延迟开始发送,等待多个Slave连接(减少重复RDB生成)
repl-diskless-sync-delay 5

适用场景

  • 磁盘IO瓶颈
  • 云环境(磁盘性能差)

五、复制状态监控

#

5.1 查看复制状态

# Master视角
redis-cli INFO replication

# 输出:
# role:master
# connected_slaves:2
# slave0:ip=192.168.1.101,port=6379,state=online,offset=123456,lag=1
# slave1:ip=192.168.1.102,port=6379,state=online,offset=123456,lag=0
# master_repl_offset:123456

# Slave视角
redis-cli INFO replication

# 输出:
# role:slave
# master_host:192.168.1.100
# master_port:6379
# master_link_status:up
# master_last_io_seconds_ago:1
# master_sync_in_progress:0
# slave_repl_offset:123456

#

5.2 监控复制延迟

# 计算复制延迟
# Master的offset - Slave的offset = 延迟字节数

# 脚本示例
#!/bin/bash
MASTER_OFFSET=$(redis-cli -h master INFO replication | grep master_repl_offset | cut -d: -f2)
SLAVE_OFFSET=$(redis-cli -h slave INFO replication | grep slave_repl_offset | cut -d: -f2)

LAG=$(($MASTER_OFFSET - $SLAVE_OFFSET))
echo "复制延迟: $LAG bytes"

if [ $LAG -gt 1000000 ]; then
echo "复制延迟过大!" | mail -s "Redis复制告警" admin@company.com
fi

#

5.3 查看Slave客户端

redis-cli CLIENT LIST | grep slave

# 输出:
# id=5 addr=192.168.1.101:port fd=8 name= age=3600 idle=1 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=replconf

六、常见问题

#

6.1 复制延迟

原因

  • 网络带宽不足
  • Master写入量过大
  • Slave性能差(单线程执行命令)
  • 大KEY操作阻塞

解决

# 1. 检查网络
ping slave_ip

# 2. 检查Slave的slowlog
redis-cli SLOWLOG GET 10

# 3. 优化大KEY操作
# 避免大Hash、大List、大ZSet的批量操作

# 4. 增加复制积压缓冲区
CONFIG SET repl-backlog-size 100mb

# 5. 使用更快的网络
# 将Master和Slave放在同一机房/可用区

#

6.2 全量同步频繁触发

原因

  • 复制积压缓冲区太小
  • Slave频繁断开重连
  • 网络不稳定

解决

# 1. 增大复制积压缓冲区
CONFIG SET repl-backlog-size 100mb

# 2. 优化网络稳定性
# 使用专线或内网连接

# 3. 检查Slave为什么断开
redis-cli INFO stats | grep sync
# sync_partial_ok: 部分同步成功次数
# sync_partial_err: 部分同步失败次数
# sync_full: 全量同步次数

#

6.3 主从数据不一致

排查

# 1. 检查复制延迟
redis-cli INFO replication | grep offset

# 2. 检查特定key
# Master
redis-cli GET key

# Slave
redis-cli GET key

# 3. 检查Slave是否可写
redis-cli CONFIG GET replica-read-only

#

6.4 复制中断

# 查看Slave日志
tail -f /var/log/redis/redis-server.log

# 常见错误:
# - MASTER aborted replication with an error: NOAUTH Authentication required.
# 解决:配置masterauth

# - MASTER disconnected
# 解决:检查网络,检查Master的最大客户端数

# - Can't SYNC while loading
# 解决:Slave正在加载RDB,等待完成

七、复制优化

#

7.1 树状复制

Master ──> Slave1 ──> Slave2

└────> Slave3 ──> Slave4

优势:减少Master的复制压力
劣势:级联延迟增加

#

7.2 复制过滤

# Slave只复制指定数据库(不推荐)
replicate-do-db 0

# Slave忽略指定命令(Redis 7.0+)
replica-ignore-disk-write-errors yes

#

7.3 读写分离

@Configuration
public class RedisConfig {

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// 主节点
RedisStaticMasterReplicaConfiguration config =
new RedisStaticMasterReplicaConfiguration("master", 6379);
// 从节点
config.addNode("slave1", 6379);
config.addNode("slave2", 6379);

return new LettuceConnectionFactory(config);
}
}

八、总结

同步方式 触发条件 数据量 对Master影响
全量同步 首次复制、offset不在缓冲区 全部数据 大(BGSAVE)
增量同步 断线重连、offset在缓冲区 部分命令
配置项 推荐值 说明
repl-backlog-size 10-100MB 根据写入量调整
repl-diskless-sync yes 无磁盘复制
min-replicas-to-write 1-2 保证至少N个Slave

Redis主从复制的核心要点:

  1. PSYNC支持增量同步,减少全量同步
  2. 复制积压缓冲区大小决定断线重连效率
  3. 监控复制延迟,及时发现问题
  4. 避免大KEY操作导致复制阻塞
  5. 考虑树状复制减轻Master压力

核心要点

  1. String:简单的键值对,适合缓存、计数器

  2. Hash:存储对象属性,适合用户信息、配置

  3. List:有序列表,适合消息队列、最新列表

  4. Set:无序去重,适合共同好友、抽奖

  5. ZSet:有序集合,适合排行榜、积分系统

总结

选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。


   转载规则


《Redis主从复制原理》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录