Redis 主从复制
2026/3/20大约 15 分钟
Redis 主从复制
一、主从复制概述
1.1 为什么需要主从复制
单机 Redis 存在以下问题:
- 单点故障:Redis 宕机导致服务不可用
- 容量限制:单机内存有限
- 性能瓶颈:单机 QPS 有上限
- 数据安全:数据只有一份,风险高
主从复制可以解决部分问题:
1.2 主从复制的特点
| 特点 | 说明 |
|---|---|
| 异步复制 | 主节点写入后立即返回,异步同步到从节点 |
| 一主多从 | 一个主节点可以有多个从节点 |
| 从节点只读 | 默认从节点不接受写操作 |
| 支持级联 | 从节点可以作为其他从节点的主节点 |
| 非阻塞同步 | 主节点在同步过程中正常处理命令 |
二、配置主从复制
2.1 方式一:配置文件
# 从节点配置文件 redis-slave.conf
# 指定主节点地址(Redis 5.0 之前用 slaveof)
replicaof 192.168.1.10 6379
# 如果主节点有密码
masterauth your_master_password
# 从节点是否只读(推荐保持默认 yes)
replica-read-only yes
# 从节点优先级(用于 Sentinel 选举,0 表示不参与选举)
replica-priority 100
# 主节点连接密码(从节点自己的密码)
requirepass your_slave_password
2.2 方式二:命令行
# 在从节点执行
redis-cli
# 设置主节点
REPLICAOF 192.168.1.10 6379
# 如果主节点有密码
CONFIG SET masterauth your_master_password
# 查看复制状态
INFO replication
# 取消复制(从节点变为独立主节点)
REPLICAOF NO ONE
2.3 方式三:启动参数
# 启动从节点时指定主节点
redis-server --replicaof 192.168.1.10 6379 --masterauth password
2.4 验证主从关系
# 在主节点查看
redis-cli -h 192.168.1.10 INFO replication
# role:master
# connected_slaves:2
# slave0:ip=192.168.1.11,port=6379,state=online,offset=1234,lag=0
# slave1:ip=192.168.1.12,port=6379,state=online,offset=1234,lag=0
# 在从节点查看
redis-cli -h 192.168.1.11 INFO replication
# role:slave
# master_host:192.168.1.10
# master_port:6379
# master_link_status:up
# master_last_io_seconds_ago:1
# master_sync_in_progress:0
三、复制原理详解
3.1 复制流程概览
Redis 的复制分为两个阶段:全量复制和增量复制。
3.2 全量复制
全量复制发生在以下情况:
- 从节点首次连接主节点
- 从节点的复制偏移量已经不在主节点的复制缓冲区中
- 主节点的 replid 与从节点记录的不一致
3.3 增量复制(部分重同步)
Redis 2.8 引入 PSYNC 命令支持增量复制:
关键参数:
# 复制积压缓冲区大小(默认 1MB)
repl-backlog-size 1mb
# 当所有从节点断开后,缓冲区的保留时间(秒)
repl-backlog-ttl 3600
缓冲区大小计算:
repl-backlog-size = 主节点写入速度 × 断线重连时间
假设:
- 主节点写入速度:2MB/s
- 预期断线重连时间:60秒
- 建议缓冲区大小:2MB/s × 60s × 2(安全系数)= 240MB
3.4 复制 ID(Replication ID)
每个 Redis 节点都有两个复制 ID:
INFO replication
# master_replid:8a9c9f6d7e8b9c0a1b2c3d4e5f6a7b8c9d0e1f2a
# master_replid2:0000000000000000000000000000000000000000
# master_repl_offset:12345
# second_repl_offset:-1
- master_replid:当前复制 ID
- master_replid2:上一个复制 ID(用于故障转移)
- master_repl_offset:复制偏移量
四、复制相关配置
4.1 主节点配置
# ===========================
# 主节点配置
# ===========================
# 访问密码
requirepass master_password
# 复制积压缓冲区大小
repl-backlog-size 128mb
# 缓冲区保留时间(秒)
repl-backlog-ttl 3600
# 当从节点少于 N 个或延迟超过 M 秒时,主节点拒绝写入
# 用于保证数据安全性
min-replicas-to-write 1
min-replicas-max-lag 10
# 磁盘化复制 vs 无盘复制
# diskless-sync no # 使用磁盘(默认)
repl-diskless-sync yes # 无盘复制(适合网络快、磁盘慢的场景)
repl-diskless-sync-delay 5 # 无盘复制延迟(秒),等待更多从节点连接
# 客户端输出缓冲区限制
# 格式:client-output-buffer-limit <class> <hard> <soft> <seconds>
client-output-buffer-limit replica 256mb 64mb 60
4.2 从节点配置
# ===========================
# 从节点配置
# ===========================
# 主节点地址
replicaof 192.168.1.10 6379
# 主节点密码
masterauth master_password
# 从节点自己的密码(可选)
requirepass slave_password
# 从节点只读
replica-read-only yes
# 从节点优先级(Sentinel 选举用,0 不参与)
replica-priority 100
# 从节点复制期间是否响应客户端(旧数据)
replica-serve-stale-data yes
# 从节点向主节点发送 PING 的间隔(秒)
repl-ping-replica-period 10
# 复制超时时间
repl-timeout 60
4.3 复制模式配置
# ===========================
# 同步相关配置
# ===========================
# 是否使用无盘加载(从节点)
repl-diskless-load disabled # disabled | on-empty-db | swapdb
# RDB 传输时是否使用 TCP_NODELAY
repl-disable-tcp-nodelay no
# 从节点复制时是否做 fsync
# 关闭可提高性能,但可能丢数据
repl-diskless-sync-max-replicas 0
五、读写分离
5.1 读写分离架构
5.2 Java 实现读写分离
使用 Jedis
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisReadWriteSplit {
private JedisPool masterPool;
private List<JedisPool> slavePools;
private AtomicInteger slaveIndex = new AtomicInteger(0);
public RedisReadWriteSplit() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(20);
// 主节点连接池
masterPool = new JedisPool(config, "192.168.1.10", 6379, 2000, "password");
// 从节点连接池列表
slavePools = Arrays.asList(
new JedisPool(config, "192.168.1.11", 6379, 2000, "password"),
new JedisPool(config, "192.168.1.12", 6379, 2000, "password")
);
}
// 写操作:发送到主节点
public void write(String key, String value) {
try (Jedis jedis = masterPool.getResource()) {
jedis.set(key, value);
}
}
// 读操作:轮询从节点
public String read(String key) {
int index = slaveIndex.getAndIncrement() % slavePools.size();
try (Jedis jedis = slavePools.get(index).getResource()) {
return jedis.get(key);
}
}
// 读操作:从主节点读(强一致性需求)
public String readFromMaster(String key) {
try (Jedis jedis = masterPool.getResource()) {
return jedis.get(key);
}
}
}
使用 Lettuce
import io.lettuce.core.ReadFrom;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.codec.StringCodec;
import io.lettuce.core.masterreplica.MasterReplica;
import io.lettuce.core.masterreplica.StatefulRedisMasterReplicaConnection;
public class LettuceReadWriteSplit {
private StatefulRedisMasterReplicaConnection<String, String> connection;
public LettuceReadWriteSplit() {
RedisClient client = RedisClient.create();
// 配置主从节点
List<RedisURI> nodes = Arrays.asList(
RedisURI.create("redis://password@192.168.1.10:6379"),
RedisURI.create("redis://password@192.168.1.11:6379"),
RedisURI.create("redis://password@192.168.1.12:6379")
);
// 创建主从连接
connection = MasterReplica.connect(client, StringCodec.UTF8, nodes);
// 设置读取策略
// MASTER: 只从主节点读
// MASTER_PREFERRED: 优先主节点
// REPLICA: 只从从节点读
// REPLICA_PREFERRED: 优先从节点
// LOWEST_LATENCY: 延迟最低的节点
// ANY: 任意节点
connection.setReadFrom(ReadFrom.REPLICA_PREFERRED);
}
public void write(String key, String value) {
// 写操作自动发送到主节点
connection.sync().set(key, value);
}
public String read(String key) {
// 根据 ReadFrom 策略选择节点
return connection.sync().get(key);
}
}
5.3 读写分离的问题
问题 1:数据延迟
解决方案:
// 方案1:写后读强制走主节点
public void writeAndRead(String key, String value) {
write(key, value);
String result = readFromMaster(key); // 从主节点读
}
// 方案2:延迟双删
public void writeWithDelay(String key, String value) {
delete(key); // 先删除
write(key, value);
Thread.sleep(500); // 等待主从同步
delete(key); // 再删除
}
// 方案3:使用 WAIT 命令等待同步
public void writeWithWait(String key, String value) {
try (Jedis jedis = masterPool.getResource()) {
jedis.set(key, value);
// 等待至少 1 个从节点同步,超时 1000ms
long synced = jedis.waitReplicas(1, 1000);
if (synced < 1) {
// 同步失败处理
}
}
}
问题 2:从节点故障
// 从节点健康检查和故障转移
public class SlaveHealthChecker {
private List<JedisPool> slavePools;
private List<Boolean> slaveStatus;
private ScheduledExecutorService scheduler;
public SlaveHealthChecker(List<JedisPool> pools) {
this.slavePools = pools;
this.slaveStatus = new ArrayList<>(Collections.nCopies(pools.size(), true));
// 定期健康检查
scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(this::healthCheck, 0, 5, TimeUnit.SECONDS);
}
private void healthCheck() {
for (int i = 0; i < slavePools.size(); i++) {
try (Jedis jedis = slavePools.get(i).getResource()) {
jedis.ping();
slaveStatus.set(i, true);
} catch (Exception e) {
slaveStatus.set(i, false);
log.warn("Slave {} is down", i);
}
}
}
public Jedis getHealthySlave() {
for (int i = 0; i < slavePools.size(); i++) {
if (slaveStatus.get(i)) {
return slavePools.get(i).getResource();
}
}
throw new RuntimeException("No healthy slave available");
}
}
六、级联复制
6.1 级联复制架构
6.2 配置级联复制
# Slave 1 配置(从 Master 复制)
replicaof 192.168.1.10 6379
masterauth master_password
# Slave 1-1 配置(从 Slave 1 复制)
replicaof 192.168.1.11 6379
masterauth slave1_password
6.3 级联复制的注意事项
- 延迟累加:级联层数越多,延迟越大
- 故障传播:中间节点故障会影响下游节点
- 适用场景:从节点数量多、主节点带宽有限时使用
七、常见问题与排查
7.1 主从连接失败
# 检查从节点日志
cat /var/log/redis/redis.log | grep -i "master"
# 常见错误:
# 1. MASTERAUTH requires password
# 解决:配置 masterauth
# 2. DENIED Redis is running in protected mode
# 解决:关闭 protected-mode 或配置 bind
# 3. Connection refused
# 解决:检查网络、防火墙、端口
# 检查主节点
redis-cli -h master_ip INFO replication
# 检查网络连通性
telnet master_ip 6379
7.2 复制延迟过大
# 查看复制延迟
redis-cli INFO replication
# 关键指标:
# master_repl_offset: 主节点偏移量
# slave0:offset=xxx,lag=10 # lag 表示延迟(秒)
# 延迟原因分析:
# 1. 网络带宽不足
# 2. 从节点负载过高
# 3. 主节点写入过快
# 4. 复制缓冲区过小
# 解决方案:
# 1. 增加网络带宽
# 2. 减少从节点负载
# 3. 增加复制缓冲区
repl-backlog-size 256mb
7.3 频繁全量复制
# 检查是否频繁全量复制
redis-cli INFO stats | grep sync
# sync_full: 全量同步次数
# sync_partial_ok: 增量同步成功次数
# sync_partial_err: 增量同步失败次数
# 如果 sync_full 增长快,说明频繁全量复制
# 原因分析:
# 1. repl-backlog-size 太小
# 2. 主节点写入速度太快
# 3. 网络频繁断开
# 解决方案:
# 增大复制缓冲区
repl-backlog-size 512mb
7.4 主节点内存暴涨
# 全量同步时主节点需要额外内存:
# 1. RDB 文件生成(fork 子进程)
# 2. 复制缓冲区(每个从节点一个)
# 监控内存
redis-cli INFO memory
# client-output-buffer-limit 配置
# 限制复制缓冲区大小
client-output-buffer-limit replica 256mb 64mb 60
# 解释:
# 256mb: 硬限制,超过立即断开
# 64mb + 60秒: 软限制,超过 64mb 持续 60 秒断开
八、运维建议
8.1 容量规划
8.2 监控指标
# 必须监控的指标
redis-cli INFO replication
# master_link_status: 主从连接状态(up/down)
# master_last_io_seconds_ago: 距离上次通信的秒数
# master_sync_in_progress: 是否正在同步
# slave_repl_offset: 从节点复制偏移量
# connected_slaves: 连接的从节点数
# 告警阈值建议:
# - master_link_status != up: 立即告警
# - master_last_io_seconds_ago > 30: 警告
# - 主从偏移量差值 > 10MB: 警告
8.3 故障处理
# 主节点故障时的手动处理
# 1. 确认主节点确实故障
redis-cli -h master_ip PING
# 2. 选择一个从节点提升为主节点
redis-cli -h slave1_ip REPLICAOF NO ONE
# 3. 其他从节点指向新主节点
redis-cli -h slave2_ip REPLICAOF new_master_ip 6379
# 4. 修改应用配置,指向新主节点
# 注意:手动故障转移容易出错,推荐使用 Sentinel
九、总结
主从复制要点
- 异步复制:写入主节点后立即返回,异步同步到从节点
- 全量 + 增量:首次全量复制,之后增量复制
- 复制缓冲区:决定能否增量复制的关键
配置建议
| 配置项 | 建议值 | 说明 |
|---|---|---|
| repl-backlog-size | 64mb-256mb | 复制缓冲区 |
| repl-timeout | 60 | 复制超时 |
| replica-priority | 100 | 从节点优先级 |
| min-replicas-to-write | 1 | 最少从节点数 |
| min-replicas-max-lag | 10 | 最大延迟 |
最佳实践
- 生产环境使用 Sentinel 自动故障转移
- 合理设置复制缓冲区大小
- 监控主从延迟和同步状态
- 从节点数量过多时考虑级联复制
- 读写分离注意数据一致性问题