跳转到内容

Redis 主从复制


单节点 Redis 存在两个核心问题:

单节点的问题:
┌──────────────┐
│ Redis 单节点 │ ← 所有读写压力集中
│ 读 + 写 │ ← 单点故障,宕机即不可用
└──────────────┘

主从复制的收益:

收益说明
读写分离Master 处理写,Slave 分担读,横向扩展读性能
数据冗余数据同步到多个节点,防止单点数据丢失
高可用基础为哨兵模式和集群模式提供故障转移能力
写请求
┌─────────────┐
│ Master │ ← 处理所有写操作
└──────┬──────┘
异步复制│
┌───────────┼───────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Slave 1 │ │ Slave 2 │ │ Slave 3 │ ← 处理读请求
└──────────┘ └──────────┘ └──────────┘
读请求 读请求 读请求

角色说明:

角色职责数量
Master(主节点)处理写操作,将数据同步给 Slave1 个
Slave(从节点)接收 Master 数据,处理读操作1 个或多个

Slave 第一次连接 Master 时,执行全量数据同步:

全量同步流程:
Slave Master
│─── replicaof <host> <port> ──▶│
│◀── 发送 replid + offset ──────│
│ │
│(第一次,replid 不匹配) │
│ ├── BGSAVE 生成 RDB 文件
│◀────────── 传输 RDB ───────────│
│ │
│── 加载 RDB ──▶│ │
│ │
│◀── 传输 RDB 期间的增量命令 ─────│ ← repl_backlog 缓冲区
│ │
│── 执行增量命令 ──▶│ │
│ │
│ 同步完成,进入持续同步阶段 │

关键参数:

# repl_backlog:环形缓冲区,存储 Master 最近的写命令
# 默认大小 1MB,用于增量同步时补发缺失命令
repl-backlog-size 1mb

Slave 短暂断线重连后,不需要全量同步,只同步缺失的命令:

增量同步条件(同时满足):
1. Slave 的 replid 与 Master 相同(同一主节点)
2. Slave 的 offset 在 repl_backlog 范围内(未被覆盖)
增量同步流程:
Slave ──▶ 发送 replid + offset
Master ──▶ 从 repl_backlog 中找到 offset 之后的命令
──▶ 只发送增量命令(非全量)

主从连接正常时,Master 每执行一条写命令,异步发送给所有 Slave:

Master 执行 SET key val
├── 写入本地内存
└── 异步发送命令给所有 Slave
Slave 接收并执行(异步,存在毫秒级延迟)

# docker-compose.yml(1 Master + 2 Slave)
version: '3'
services:
redis-master:
image: redis:7.0
container_name: redis-master
ports:
- "6379:6379"
command: redis-server --requirepass 123456 --appendonly yes
redis-slave1:
image: redis:7.0
container_name: redis-slave1
ports:
- "6380:6379"
command: >
redis-server
--requirepass 123456
--masterauth 123456
--replicaof redis-master 6379
--appendonly yes
redis-slave2:
image: redis:7.0
container_name: redis-slave2
ports:
- "6381:6379"
command: >
redis-server
--requirepass 123456
--masterauth 123456
--replicaof redis-master 6379
--appendonly yes

验证主从同步:

Terminal window
# 在 Master 写入
redis-cli -p 6379 -a 123456 SET test_key "hello"
# 在 Slave 读取(数据应已同步)
redis-cli -p 6380 -a 123456 GET test_key # → "hello"
redis-cli -p 6381 -a 123456 GET test_key # → "hello"
# 查看主从状态
redis-cli -p 6379 -a 123456 INFO replication

spring:
data:
redis:
# 主节点(写)
host: 192.168.1.10
port: 6379
password: 123456
lettuce:
cluster:
refresh:
adaptive: true

手动配置读写分离(Lettuce):

@Configuration
public class RedisReadWriteConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// Master 节点(写)
RedisStandaloneConfiguration master =
new RedisStandaloneConfiguration("192.168.1.10", 6379);
master.setPassword("123456");
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED) // 优先从 Slave 读,Slave 不可用时从 Master 读
.build();
return new LettuceConnectionFactory(master, clientConfig);
}
}

ReadFrom 策略:

策略说明
MASTER只从 Master 读
REPLICA只从 Slave 读,无 Slave 时报错
REPLICA_PREFERRED优先 Slave,无 Slave 时从 Master 读 ✅
NEAREST从延迟最低的节点读

问题说明解决方案
Master 单点故障Master 宕机后无法写入,需人工切换哨兵模式(自动故障转移)
异步复制数据丢失Master 宕机前未同步到 Slave 的数据丢失min-replicas-to-write 配置
扩展性有限单 Master 写入能力有上限Redis Cluster(数据分片)

最小同步副本数(减少数据丢失):

# redis.conf(Master 配置)
# 要求至少 1 个 Slave 完成同步才响应写请求
# 若 Slave 数量不足或同步延迟超过 10s,Master 拒绝写入
min-replicas-to-write 1
min-replicas-max-lag 10