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 复制作用
- 数据冗余:多份数据副本,防止数据丢失
- 读写分离:主节点写,从节点读
- 故障恢复:主节点故障,从节点可接管
- 高可用基础:哨兵模式和集群的基础
二、主从配置
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 全量复制
触发条件:
- 从节点首次连接
- 复制断线时间过长
- 复制 ID 不匹配
流程:
从节点 主节点
│ │
│────── 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 增量复制
触发条件:
- 短暂网络断开
- 复制 ID 匹配
- 缺失的数据在复制缓冲区中
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 延迟原因
- 网络延迟:主从之间网络慢
- 全量复制:RDB 传输耗时
- 从节点性能:从节点处理慢
- 大键操作:单个命令执行时间长
- 主节点压力:主节点写压力大
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 | 唯一标识主节点 |
| 偏移量 | 记录复制进度 |
最佳实践:
- 一主多从架构
- 开启持久化
- 配置合理的超时时间
- 监控复制延迟
- 使用哨兵实现高可用
掌握主从复制,是构建高可用 Redis 架构的基础!