Skip to content

主从复制原理:全量同步(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. 同步完成,进入增量复制阶段
 */

全量同步的开销

开销说明
CPUBGSAVE 消耗 CPU 进行序列化
内存COW 机制可能导致内存翻倍
磁盘RDB 写入磁盘
网络传输 RDB 文件(可能很大)

增量同步(PSYNC)

原理

全量同步开销大,所以 Redis 引入了 PSYNC(Partial Sync)

  • 完整同步:从节点数据丢失太多,需要全量同步
  • 部分同步:从节点数据丢失不多,只同步增量命令
┌─────────────┐                                        ┌─────────────┐
│    从节点    │                                        │    主节点    │
└─────────────┘                                        └─────────────┘
       │                                                       │
       │  断线重连                                              │
       │                                                       │
       │────────── PSYNC <runid> <offset> ──────────────────▶ │
       │                                                       │
       │                     │ 检查 offset 是否在缓冲区范围内    │
       │                     ▼                                 │
       │              ┌─────────────┐                         │
       │              │ offset 在   │ ──── YES ───→ 返回 +命令 │
       │              │ 缓冲区?     │                         │
       │              └─────────────┘                         │
       │                    │                                  │
       │                    │ NO                               │
       │                    ▼                                 │
       │              ┌─────────────┐                         │
       │              │  执行完整   │ ──────────────────────── │
       │              │  同步       │                         │
       │              └─────────────┘                         │

部分同步的条件

部分同步需要满足:

  1. 从节点记录的 runid = 主节点 runid:主节点没变
  2. 从节点的复制偏移量在主节点复制积压缓冲区范围内:丢失的数据不多

复制积压缓冲区(Replication Backlog)

主节点维护一个 FIFO 环形缓冲区

┌─────────────────────────────────────────────────────────────────┐
│                      复制积压缓冲区                               │
│                                                                 │
│   [命令1] [命令2] [命令3] [命令4] ... [命令N]                  │
│       ↑                                               ↑          │
│       └───────────────────────────────────────────────┘          │
│                        环形覆盖                                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

配置:

bash
# 积压缓冲区大小(建议:每秒写入量 × 平均断线时间 × 2)
repl-backlog-size 10mb

# 如果所有从节点都断开,积压缓冲区保留时间
repl-backlog-ttl 3600

PSYNC 的三种情况

情况条件结果
完整同步从节点没有 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 范围内的命令,开销小
  • 积压缓冲区:环形缓冲区,保存最近的增量命令
  • 读写分离:主写从读,分担压力

留给你的问题

主从复制是异步的,这意味着从节点的数据可能落后于主节点。

如果你的业务要求「写入后立刻读取」,使用主从复制会有什么风险?应该如何解决?

基于 VitePress 构建