Skip to content
清晨的一缕阳光
返回

Redis 主从复制原理

Redis 主从复制原理

主从复制是 Redis 高可用的基础。通过复制机制,可以实现数据冗余、读写分离和故障恢复。本文将深入解析主从复制的完整流程,揭示其核心原理和 optimization 技巧。

一、主从复制概述

1.1 什么是主从复制

主从复制是将一个 Redis 服务器(主节点)的数据复制到一个或多个 Redis 服务器(从节点)的过程。

┌─────────────┐
│   Master    │  主节点(写)
│  192.168.1.1│
│   port 6379 │
└──────┬──────┘
       │ 复制
       ├───┬───────────────┐
       ▼   ▼               ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Slave 1 │ │ Slave 2 │ │ Slave 3 │  从节点(读)
│ 192.168 │ │ 192.168 │ │ 192.168 │
│  .1.2   │ │  .1.3   │ │  .1.4   │
└─────────┘ └─────────┘ └─────────┘

1.2 复制特性

特性说明
异步复制主节点不等待从节点确认
单向复制只能从主到从
树状结构从节点可以有下级的从节点
自动恢复网络断开后自动重连

1.3 复制作用

  1. 数据冗余:多份数据副本,防止数据丢失
  2. 读写分离:主节点写,从节点读
  3. 故障恢复:主节点故障,从节点可接管
  4. 高可用基础:哨兵模式和集群的基础

二、主从配置

2.1 配置方式

方式 1:配置文件

# 从节点配置
replicaof 192.168.1.1 6379

# Redis 5.0 之前使用 slaveof
slaveof 192.168.1.1 6379

方式 2:命令行

# 动态设置主节点
SLAVEOF 192.168.1.1 6379

# 取消主从关系(变为主节点)
SLAVEOF NO ONE

2.2 验证复制状态

# 查看复制信息
INFO replication

# 输出示例
# Replication
# role:slave
# master_host:192.168.1.1
# master_port:6379
# master_link_status:up
# master_last_io_seconds_ago:1
# slave_repl_offset:123456
# slave_priority:100
# slave_read_only:1

# 查看主节点信息
INFO clients
# connected_slaves:2

三、复制流程详解

3.1 完整复制流程

从节点启动

1. 保存主节点信息

2. 建立 socket 连接

3. 发送 PING 命令

4. 身份验证(如有密码)

5. 发送 REPLICATION ID

6. 判断全量/增量复制

7. 数据传输

8. 进入命令传播阶段

3.2 全量复制

触发条件

流程

从节点                     主节点
  │                          │
  │────── PSYNC ? -1 ───────▶│  1. 发送 PSYNC 请求
  │                          │
  │◀───── +FULLRESYNC ──────│  2. 返回 FULLRESYNC
  │                          │
  │◀───── BGSAVE ───────────│  3. 执行 BGSAVE
  │                          │
  │◀───── RDB 文件 ─────────│  4. 发送 RDB 文件
  │                          │
  │◀───── 复制缓冲区 ───────│  5. 发送复制缓冲区命令
  │                          │
  │────── ACK offset ──────▶│  6. 确认偏移量
  │                          │
  │◀───── 持续复制 ─────────│  7. 进入命令传播

源码分析

// 主节点处理 PSYNC 命令
void syncCommand(client *c) {
    // 1. 检查是否可以增量复制
    if (canPartialResync(c)) {
        // 增量复制
        partialResync(c);
    } else {
        // 2. 全量复制
        fullResync(c);
    }
}

// 全量复制
void fullResync(client *c) {
    // 1. 创建 RDB 快照
    rdbSaveInfo *rsi;
    rdbSaveBackground(server.rdb_filename, &rsi);
    
    // 2. 将写命令放入缓冲区
    while(rdb saving) {
        put clients write commands to replication buffer;
    }
    
    // 3. 发送 RDB 文件
    send RDB to slave;
    
    // 4. 发送缓冲区命令
    send replication buffer to slave;
}

3.3 增量复制

触发条件

PSYNC 命令

# 从节点发送
PSYNC <replication_id> <offset>

# 示例
PSYNC 893750e425182959a2b1660d63938429cd428095 123456

# 主节点响应
+PARTIALRESYNC <replication_id> <offset>  # 支持增量
+FULLRESYNC <replication_id> <offset>     # 需要全量

流程

从节点                     主节点
  │                          │
  │── PSYNC id offset ─────▶│  1. 发送 PSYNC
  │                          │
  │◀─ +PARTIALRESYNC ──────│  2. 确认可增量
  │                          │
  │◀─ 缺失的命令 ──────────│  3. 发送缺失命令
  │                          │
  │────── ACK ─────────────▶│  4. 确认偏移量

3.4 复制 ID 和偏移量

复制 ID(Replication ID)

每个主节点有唯一的 replication id
主节点切换时,replication id 会变化

格式:40 位十六进制字符串
示例:893750e425182959a2b1660d63938429cd428095

偏移量(Offset)

记录复制的进度
主节点和从节点各自维护 offset

主节点:master_repl_offset
从节点:slave_repl_offset

通过比较 offset 判断数据同步进度

四、心跳检测

4.1 心跳机制

主节点                     从节点
  │                          │
  │◀───── PING ────────────│  每秒发送
  │                          │
  │───── PONG ────────────▶│  回复
  │                          │
  │◀───── REPCONF ─────────│  汇报状态
  │   offset, priority      │

4.2 心跳内容

从节点发送

# 每秒发送
REPLCONF ACK <offset>

# 示例
REPLCONF ACK 123456

主节点检测

// 检查从节点超时
for each slave {
    if (now - slave->repl_ack_time > repl_ping_slave_period * 2) {
        // 标记为超时
        slave->flags |= CLIENT_SLAVE_STALLED;
    }
}

4.3 超时配置

# 主节点配置
repl-ping-replica-period 10     # PING 间隔(秒)
repl-timeout 60                 # 超时时间(秒)

# 从节点配置
repl-ping-primary-period 10
repl-timeout 60

五、命令传播

5.1 写命令传播

客户端 ──▶ 主节点 ──▶ 从节点 1

              └──▶ 从节点 2

              └──▶ 从节点 3

所有写命令都会复制到从节点

5.2 复制缓冲区

// 复制缓冲区结构
struct client {
    // ...
    list *reply;              // 输出缓冲区
    unsigned long long repl_ack_off;  // 确认的偏移量
    // ...
};

// 写命令处理
void feedReplicationBuffer(char *cmd, int len) {
    // 1. 添加到复制缓冲区
    for each slave {
        add command to slave->reply;
    }
    
    // 2. 更新偏移量
    server.master_repl_offset += len;
}

5.3 异步复制

主节点执行写命令

立即返回客户端

异步发送到从节点

注意:主节点不等待从节点确认
这可能导致数据不一致(主从延迟)

六、主从延迟

6.1 延迟原因

  1. 网络延迟:主从之间网络慢
  2. 全量复制:RDB 传输耗时
  3. 从节点性能:从节点处理慢
  4. 大键操作:单个命令执行时间长
  5. 主节点压力:主节点写压力大

6.2 延迟监控

# 查看延迟
INFO replication
# master_repl_offset: 1000000
# slave_repl_offset: 999000
# 延迟 = 1000 bytes

# 实时监控
redis-cli --replication

# 监控工具
redis-cli INFO | grep offset

6.3 延迟优化

优化 1:网络优化

# 同一机房部署
# 使用内网通信
# 增加带宽

优化 2:减少大键

# 避免大键操作
# ❌ 不推荐
KEY *                    # 全量扫描
KEYS user:*              # 可能返回大量数据

# ✅ 推荐
SCAN 0 MATCH user:* COUNT 100  # 分批扫描

优化 3:异步刷盘

# 从节点配置
appendfsync no             # 不刷盘,提升性能

优化 4:调整缓冲区

# 增加复制缓冲区
client-output-buffer-limit slave 256mb 64mb 60

七、读写分离

7.1 配置读写分离

# 从节点配置
replica-read-only yes      # 只读(默认)

# 允许从节点写(不推荐)
replica-read-only no

7.2 客户端实现

# Python 示例
from redis import Redis
from redis.sentinel import Sentinel

# 主节点(写)
master = Redis(host='192.168.1.1', port=6379)

# 从节点(读)
slave = Redis(host='192.168.1.2', port=6379)

# 写操作
master.set('key', 'value')

# 读操作
value = slave.get('key')

7.3 一致性问题

写主节点

复制到从节点(延迟)

从节点读(可能读到旧数据)

解决方案:
1. 关键数据直接读主节点
2. 等待复制完成再读
3. 使用哨兵或集群

八、故障处理

8.1 主节点故障

主节点宕机

从节点检测到连接断开

等待 repl-timeout 时间

尝试重连

失败则选举新主节点(哨兵模式)

8.2 从节点故障

从节点宕机

主节点检测到连接断开

继续服务其他从节点

从节点恢复后自动重连

增量复制缺失数据

8.3 网络分区

网络分区

主从分离

主节点继续写

从节点数据落后

网络恢复后全量/增量复制

九、最佳实践

9.1 部署建议

推荐架构:
          ┌─────────────┐
          │   Master    │
          │  192.168.1.1│
          └──────┬──────┘

        ┌────────┼────────┐
        ▼        ▼        ▼
   ┌─────────┐ ┌─────────┐ ┌─────────┐
   │ Slave 1 │ │ Slave 2 │ │ Slave 3 │
   │ 192.168 │ │ 192.168 │ │ 192.168 │
   │  .1.2   │ │  .1.3   │ │  .1.4   │
   └─────────┘ └─────────┘ └─────────┘
        │           │           │
        └───────────┼───────────┘

              负载均衡器

9.2 配置优化

# 主节点配置
maxmemory 4gb
maxmemory-policy allkeys-lru
repl-backlog-size 256mb      # 复制缓冲区
repl-backlog-ttl 3600        # 缓冲区保留时间

# 从节点配置
replica-read-only yes
repl-ping-primary-period 10
repl-timeout 60

9.3 监控指标

# 关键指标
INFO replication
- master_link_status         # 连接状态
- master_last_io_seconds_ago # 最后通信时间
- slave_repl_offset          # 复制偏移量

# 告警阈值
master_link_status != up     # 连接断开
master_last_io_seconds_ago > 60  # 通信超时
offset 差值 > 1MB            # 复制延迟

十、常见问题

Q1: 复制失败怎么办?

# 检查网络
telnet master_host master_port

# 检查密码
CONFIG GET requirepass

# 检查复制状态
INFO replication

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

Q2: 全量复制频繁?

# 增加复制缓冲区
repl-backlog-size 512mb

# 延长缓冲区保留时间
repl-backlog-ttl 7200

# 检查网络稳定性

Q3: 从节点数据不一致?

# 手动触发全量复制
SLAVEOF NO ONE
SLAVEOF master_host master_port

# 检查主从偏移量
INFO replication

# 等待复制完成
WAIT 1 5000

总结

Redis 主从复制核心要点:

机制说明
全量复制首次同步或断线时间长
增量复制短暂断线后同步
心跳检测每秒 PING/PONG
命令传播异步复制写命令
复制 ID唯一标识主节点
偏移量记录复制进度

最佳实践

  1. 一主多从架构
  2. 开启持久化
  3. 配置合理的超时时间
  4. 监控复制延迟
  5. 使用哨兵实现高可用

掌握主从复制,是构建高可用 Redis 架构的基础!

参考资料


分享这篇文章到:

上一篇文章
RocketMQ 生产实践案例精选
下一篇文章
RocketMQ 生产问题排查案例集