主从复制原理:全量同步(SYNC)与增量同步(PSYNC)
双十一零点,你的 Redis 扛不住了。
QPS 从 10 万暴涨到 100 万,一台机器完全不够用。
怎么办?
主从复制来救场。
什么是主从复制?
主从复制是指:一个 Redis 节点(主节点)的数据,自动复制到其他 Redis 节点(从节点)。
┌─────────────────────────────────────────────────────────────────┐
│ 主节点(Master) │
│ │
│ 接收写请求 │
│ 存储所有数据 │
│ 复制数据到从节点 │
└─────────────────────────────────────────────────────────────────┘
│ │ │
│ 复制 │ 复制 │ 复制
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 从节点 1 │ │ 从节点 2 │ │ 从节点 3 │
│ (Slave 1) │ │ (Slave 2) │ │ (Slave 3) │
└───────────────┘ └───────────────┘ └───────────────┘从节点只能读,不能写。所有写操作都发送到主节点。
主从复制的用途
| 用途 | 说明 |
|---|---|
| 读写分离 | 主节点写,从节点读,分担压力 |
| 数据备份 | 从节点作为数据副本,防止数据丢失 |
| 故障恢复 | 主节点宕机,从节点可以接管 |
| 水平扩展 | 添加更多从节点,分担读压力 |
主从复制的配置
命令行配置
bash
# 在从节点上执行
redis-cli replicaof 127.0.0.1 6379
# 取消复制,成为独立节点
redis-cli replicaof no one配置文件配置
bash
# redis.conf
# 主节点地址
replicaof 127.0.0.1 6379
# 主节点密码(如果有)
masterauth <password>
# 从节点只读(默认开启)
replica-serve-stale-data yes
# 从节点是否可写
replica-read-only yes
# 同步缓冲区大小
repl-backlog-size 10mb
# 复制超时时间
repl-timeout 60全量同步(SYNC)
原理
当从节点第一次连接主节点,或者断线重连后数据不一致时,需要执行全量同步:
┌─────────────┐ ┌─────────────┐
│ 从节点 │ │ 主节点 │
└─────────────┘ └─────────────┘
│ │
│────────── REPLCONF listening-port 6379 ───────────▶ │
│────────── PING ────────────────────────────────────▶ │
│◀───────── PONG ────────────────────────────────────── │
│ │
│────────── AUTH (如果需要) ──────────────────────────▶ │
│ │
│────────── PSYNC ? -1 ───────────────────────────────▶ │
│ │ 1. BGSAVE
│ │ 2. 生成 RDB
│ │ 3. 记录命令
│◀───────── FULLRESYNC <runid> <offset> ──────────────── │
│ │
│◀══════════════ RDB 文件 ═════════════════════════════ │
│ │
│────────── ACK <offset> ──────────────────────────────▶ │
│◀══════════ 缓冲区中的增量命令 ═════════════════════════ │
│ │
│ 全量同步完成,开始正常复制 │全量同步的步骤
java
/**
* Redis 全量同步(SYNC)流程:
*
* 1. 从节点发送 PSYNC ? -1 请求(询问主节点能否部分同步)
* 2. 主节点判断需要全量同步,返回 FULLRESYNC
* 3. 主节点执行 BGSAVE,在后台生成 RDB 快照
* 4. 主节点一边接收写命令,一边写入 replication buffer
* 5. RDB 生成后,主节点发送给从节点
* 6. 从节点清空本地数据,加载 RDB
* 7. 主节点将 replication buffer 中的增量命令发送给从节点
* 8. 同步完成,进入增量复制阶段
*/全量同步的开销
| 开销 | 说明 |
|---|---|
| CPU | BGSAVE 消耗 CPU 进行序列化 |
| 内存 | COW 机制可能导致内存翻倍 |
| 磁盘 | RDB 写入磁盘 |
| 网络 | 传输 RDB 文件(可能很大) |
增量同步(PSYNC)
原理
全量同步开销大,所以 Redis 引入了 PSYNC(Partial Sync):
- 完整同步:从节点数据丢失太多,需要全量同步
- 部分同步:从节点数据丢失不多,只同步增量命令
┌─────────────┐ ┌─────────────┐
│ 从节点 │ │ 主节点 │
└─────────────┘ └─────────────┘
│ │
│ 断线重连 │
│ │
│────────── PSYNC <runid> <offset> ──────────────────▶ │
│ │
│ │ 检查 offset 是否在缓冲区范围内 │
│ ▼ │
│ ┌─────────────┐ │
│ │ offset 在 │ ──── YES ───→ 返回 +命令 │
│ │ 缓冲区? │ │
│ └─────────────┘ │
│ │ │
│ │ NO │
│ ▼ │
│ ┌─────────────┐ │
│ │ 执行完整 │ ──────────────────────── │
│ │ 同步 │ │
│ └─────────────┘ │部分同步的条件
部分同步需要满足:
- 从节点记录的 runid = 主节点 runid:主节点没变
- 从节点的复制偏移量在主节点复制积压缓冲区范围内:丢失的数据不多
复制积压缓冲区(Replication Backlog)
主节点维护一个 FIFO 环形缓冲区:
┌─────────────────────────────────────────────────────────────────┐
│ 复制积压缓冲区 │
│ │
│ [命令1] [命令2] [命令3] [命令4] ... [命令N] │
│ ↑ ↑ │
│ └───────────────────────────────────────────────┘ │
│ 环形覆盖 │
│ │
└─────────────────────────────────────────────────────────────────┘配置:
bash
# 积压缓冲区大小(建议:每秒写入量 × 平均断线时间 × 2)
repl-backlog-size 10mb
# 如果所有从节点都断开,积压缓冲区保留时间
repl-backlog-ttl 3600PSYNC 的三种情况
| 情况 | 条件 | 结果 |
|---|---|---|
| 完整同步 | 从节点没有 runid 或 offset,或丢失太多 | 全量同步 |
| 部分同步 | offset 在积压缓冲区范围内 | 只发送增量命令 |
| 不支持 | 主节点不支持 PSYNC(老版本) | 全量同步 |
主从复制的配置参数
bash
# redis.conf
# ==================== 主从复制 ====================
# 从节点配置
replicaof 127.0.0.1 6379
masterauth <password>
# 同步策略
repl-diskless-sync no # 是否使用无盘复制
repl-diskless-sync-delay 5 # 无盘复制延迟
# 积压缓冲区
repl-backlog-size 10mb
repl-backlog-ttl 3600
# 从节点优先级(用于 Sentinel 选主)
replica-priority 100
# 复制超时
repl-timeout 60
# 是否开启 TCP_NODELAY
repl-disable-tcp-nodelay no主从复制的工作流程
完整流程图
┌─────────────────────────────────────────────────────────────────┐
│ 从节点启动 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. 建立网络连接 │
│ │
│ - 保存主节点 IP 和端口 │
│ - 建立 socket 连接 │
│ - 建立心跳连接 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. 身份认证(可选) │
│ │
│ - 如果 masterauth 配置,执行 AUTH │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. 发送 PING │
│ │
│ - 检查主节点是否可用 │
│ - 检测网络延迟 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. 发送 PSYNC 请求 │
│ │
│ - 带上 runid 和 offset │
│ - 主节点决定同步方式 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. 执行同步 │
│ │
│ - FULLRESYNC → 全量同步(生成 RDB + 发送 + 加载) │
│ - +CONTINUE → 部分同步(发送增量命令) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. 持续复制 │
│ │
│ - 接收主节点发来的命令 │
│ - 在本地执行命令 │
│ - 维护复制偏移量 offset │
└─────────────────────────────────────────────────────────────────┘Java 客户端实现
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.params.SetParams;
public class RedisReplicationDemo {
private JedisPool masterPool;
private JedisPool[] slavePools;
/**
* 读写分离
*
* 写操作:发送到主节点
* 读操作:轮询发送到从节点(可配置权重)
*/
public String readFromSlave(String key) {
// 简单轮询选择从节点
Jedis slave = slavePools[(int)(System.currentTimeMillis() % slavePools.length)]
.getResource();
try {
return slave.get(key);
} finally {
slave.close();
}
}
public String writeToMaster(String key, String value) {
Jedis master = masterPool.getResource();
try {
return master.set(key, value);
} finally {
master.close();
}
}
/**
* 监控复制状态
*/
public void monitorReplication(Jedis jedis) {
// 查看主节点复制信息
String info = jedis.info("replication");
System.out.println(info);
// 查看复制延迟
String role = jedis.role(); // [master/slave, offset, [slaves...]]
System.out.println("角色: " + role);
}
}主从复制的常见问题
问题 1:主节点宕机
现象:从节点仍然存活,但无法写入。
解决:使用 Sentinel 实现自动故障转移。
问题 2:复制延迟
现象:从节点数据比主节点慢。
原因:
- 网络带宽不足
- 从节点负载过高
- 积压缓冲区太小
解决:
bash
# 增大积压缓冲区
repl-backlog-size 100mb
# 监控延迟
redis-cli INFO replication | grep slave0问题 3:复制风暴
现象:一个主节点挂了,多个从节点同时重连,导致主节点压力过大。
解决:
- 限制同时复制的从节点数量
- 使用层级复制(主-从-从)
- 配置合理的超时时间
问题 4:数据不一致
现象:主从数据短暂不一致。
原因:
- 网络延迟
- 命令传播延迟
- 从节点处理速度慢
解决:使用最终一致性模型,接受短暂不一致。
面试追问方向
| 问题 | 考察点 |
|---|---|
| SYNC 和 PSYNC 的区别? | 全量 vs 增量复制 |
| 为什么需要 replication backlog? | 增量复制原理 |
| 主节点宕机了怎么办? | Sentinel 自动故障转移 |
| 复制延迟怎么处理? | 读写分离策略 |
| 主从复制能保证强一致性吗? | CAP 定理、最终一致性 |
总结
主从复制是 Redis 高可用的基础:
- 全量同步:第一次连接或数据丢失太多时执行,开销大
- 增量同步:只同步 offset 范围内的命令,开销小
- 积压缓冲区:环形缓冲区,保存最近的增量命令
- 读写分离:主写从读,分担压力
留给你的问题
主从复制是异步的,这意味着从节点的数据可能落后于主节点。
如果你的业务要求「写入后立刻读取」,使用主从复制会有什么风险?应该如何解决?
