Redis基础:5. 主从复制
Redis 主从复制:从单机孤胆到集群作战
一台 Redis 挂了怎么办?没关系,我们还有千千万万台!
想象一下这个场景:你的 Redis 单机跑得好好的,每天处理千万级请求。突然某个深夜,服务器内存坏了,Redis 进程崩溃。你的监控系统疯狂报警,用户开始反馈页面加载失败——因为所有缓存都丢了,数据库被活活打死。
你从睡梦中惊醒,手忙脚乱地重启 Redis。但恢复数据需要时间,这十分钟里,公司损失了多少钱?
单点故障,是每个架构师的噩梦。
今天我们就来聊聊如何让 Redis 从"孤胆英雄"变成"复仇者联盟"——主从复制。
一、 什么是主从复制?为什么要用?
1.1 一句话定义
主从复制(Replication):将一台 Redis 服务器的数据,自动同步到其他多台 Redis 服务器。前者叫Master(主节点),后者叫Slave/Replica(从节点)。
1.2 三大核心价值
| 价值 | 说明 | 场景 |
|---|---|---|
| 高可用 | Master 挂了,Slave 可以顶上去 | 避免单点故障 |
| 读写分离 | Master 写,Slave 读 | 提高吞吐量 |
| 数据备份 | Slave 可以作为热备份 | 防止数据丢失 |
真实案例:
- 某电商网站在双十一期间,读 QPS 达到 50 万+。单机 Redis 绝对扛不住,但1 主 5 从,把读请求分散到 5 台 Slave 上,轻松应对。
- 某社交平台,Master 突然宕机。哨兵(Sentinel)在 10 秒内自动把 Slave 提升为 Master,用户几乎无感知。
二、 主从复制的核心原理
2.1 三大阶段
主从复制分为三个阶段:建立连接 → 全量同步 → 增量同步
- 建立连接
Slave → Master: PSYNC ? -1
Master → Slave: +FULLRESYNC - 全量同步(第一次或 offset 太落后)
Master: BGSAVE 生成 RDB
Master → Slave: 发送 RDB 文件
Slave: 清空旧数据,加载 RDB - 增量同步(正常运行中)
Master: 将写命令写入 replication buffer
Master → Slave: 持续发送写命令
Slave: 执行写命令,保持同步
2.2 全量同步(Full Synchronization)
触发条件:
- Slave 第一次连接 Master
- Slave 的复制 offset 落后太多(Master 的 backlog 已覆盖不到)
- 执行了
SLAVEOF NO ONE后重新配置
详细流程:
# Step 1: Slave 发送 PSYNC 命令Slave: PSYNC ?-1# Step 2: Master 返回 FULLRESYNCMaster: +FULLRESYNC<master_replid><offset># Step 3: Master 执行 BGSAVE,生成 RDB 快照Master: BGSAVE (fork 子进程)# Step 4: Master 把 RDB 文件发送给 SlaveMaster → Slave: RDB 文件(二进制)# Step 5: Slave 清空旧数据,加载 RDBSlave: FLUSHALL Slave: 加载 RDB 到内存# Step 6: Master 把 RDB 生成期间的写命令发给 SlaveMaster → Slave: replication buffer 中的命令# Step 7: Slave 执行这些命令,追上 Master注意:全量同步非常耗时!如果 RDB 文件有 10GB,网络传输 + 加载可能需要几分钟。期间 Slave 无法提供服务。
2.3 增量同步(Partial Synchronization)
Redis 2.8 引入了部分同步,解决了网络闪断后必须全量同步的低效问题。
核心机制:
- Master 维护一个replication backlog(环形缓冲区,默认 1MB)
- 所有写命令同时写入 backlog
- Slave 断线重连后,发送
PSYNC <runid> <offset> - Master 检查 offset 是否还在 backlog 中
- ✅ 如果在:发送
+CONTINUE,然后发送 backlog 中的增量命令 - ❌ 如果不在:退化为全量同步
- ✅ 如果在:发送
┌─────────────────────────────────────┐ │ Master Replication Backlog │ │ (环形缓冲区,默认 1MB) │ ├─────────────────────────────────────┤ │ [cmd1][cmd2][cmd3]...[cmdN] │ │ ↑ ↑ │ │ Slave offset Master offset │ └─────────────────────────────────────┘配置建议:
# redis.conf # 增大 backlog,避免网络抖动导致全量同步 repl-backlog-size 100mb # 在没有 Slave 的情况下,多久释放 backlog(0 = 永不释放) repl-backlog-ttl 3600三、 环境搭建:从零开始配置主从
3.1 快速搭建(3 台机器,或者 1 台机器 3 个端口)
方案一:3 台物理机
- Master:192.168.1.100:6379
- Slave1:192.168.1.101:6379
- Slave2:192.168.1.102:6379
方案二:单机 3 个实例(演示用)
# 1. 创建三个配置文件cpredis.conf redis-6379.confcpredis.conf redis-6380.confcpredis.conf redis-6381.conf# redis-6379.conf(Master) port 6379 daemonize yes pidfile /var/run/redis-6379.pid logfile /var/log/redis-6379.log dir /var/lib/redis-6379 # redis-6380.conf(Slave) port 6380 daemonize yes pidfile /var/run/redis-6380.pid logfile /var/log/redis-6380.log dir /var/lib/redis-6380 # 关键配置:指定 Master replicaof 127.0.0.1 6379 # redis-6381.conf(Slave) port 6381 daemonize yes pidfile /var/run/redis-6381.pid logfile /var/log/redis-6381.log dir /var/lib/redis-6381 replicaof 127.0.0.1 6379# 2. 启动三个实例redis-server redis-6379.conf redis-server redis-6380.conf redis-server redis-6381.conf# 3. 验证主从关系redis-cli-p6379INFO replication3.2 动态配置(不停机添加从库)
# 在 Slave 上执行redis-cli-p6380127.0.0.1:6380>REPLICAOF127.0.0.16379OK# 取消复制(Slave 变 Master)127.0.0.1:6380>REPLICAOF NO ONE OK3.3 验证主从同步
# 在 Master 上写数据redis-cli-p6379SET name"Redis Master"OK# 在 Slave 上读取redis-cli-p6380GET name"Redis Master"# 在 Slave 上尝试写(应该失败)redis-cli-p6380SET age18(error)READONLY You can'twriteagainst areadonly replica.四、 核心配置参数详解
4.1 Master 配置
# 主从复制相关配置(在 Master 上) # 设置密码(推荐) requirepass master_password # 允许哪些 Slave 连接(默认所有) # masterauth 在 Slave 上配置4.2 Slave 配置
# 从库配置 # 指定主库(必配) replicaof 192.168.1.100 6379 # 主库密码(如果主库设置了) masterauth master_password # 从库是否只读(默认 yes,强烈建议保持) replica-read-only yes # 主从断开后,是否继续响应读请求 # yes = 返回旧数据,no = 返回 error replica-serve-stale-data yes # 优先级(哨兵选主时,值越小越优先,0 表示永远不会被选为主) replica-priority 1004.3 网络优化配置
# 复制超时时间(秒) repl-timeout 60 # 复制缓冲区大小(影响部分同步的成功率) repl-backlog-size 100mb # 是否使用无盘复制(直接通过网络发送 RDB,不落盘) # 适合磁盘慢但网络快的场景 repl-diskless-sync yes # 无盘复制的延迟时间(等待更多 Slave 一起同步) repl-diskless-sync-delay 5五、 复制拓扑:三种主流架构
5.1 一主一从
Master → Slave适用:小规模应用,备份为主
优点:简单
缺点:读压力大时 Slave 成为瓶颈
5.2 一主多从(星型)
┌─── Slave1 Master ─┼─── Slave2 └─── Slave3适用:读多写少的场景(如排行榜、缓存)
优点:读写分离,读性能线性提升
缺点:Master 写压力不变,网络带宽占用大
5.3 级联复制(树型)
Master → Slave1 → Slave2 └──→ Slave3适用:跨机房部署(主库写,从库级联到其他机房)
优点:减轻 Master 的网络压力
缺点:链路越长,延迟越大
配置级联:
# Slave1(同时作为 Slave2 的 Master)127.0.0.1:6380>REPLICAOF192.168.1.1006379# Slave2127.0.0.1:6381>REPLICAOF192.168.1.1016380# 指向 Slave1六、 监控与运维
6.1 查看复制状态
# 查看主库的复制状态redis-cli-p6379INFO replication# 输出示例# Role:master# connected_slaves:2# slave0:ip=127.0.0.1,port=6380,state=online,offset=12345,lag=0# slave1:ip=127.0.0.1,port=6381,state=online,offset=12345,lag=0# master_replid:3a9c1d7e...# master_repl_offset:12345# 查看从库的复制状态redis-cli-p6380INFO replication# 输出示例# Role:slave# master_host:127.0.0.1# master_port:6379# master_link_status:up# slave_repl_offset:12345# master_last_io_seconds_ago:0关键指标:
master_link_status:up(正常)/ down(断开)lag:从库延迟的秒数(理想是 0)slave_repl_offset:从库的复制偏移量
6.2 手动触发同步
# 在从库上强制全量同步redis-cli-p6380SLAVEOF NO ONE# 断开redis-cli-p6380SLAVEOF127.0.0.16379# 重连(会触发全量)6.3 常见问题排查
问题 1:Slave 延迟过高(lag > 5)
原因:
- Slave 机器性能差(CPU/内存/网络)
- Master 写 QPS 太高,Slave 执行跟不上
- 网络带宽不足
解决:
- 升级 Slave 配置
- 增加级联复制,分担压力
- 调整
repl-backlog-size
问题 2:主从断开后重连触发全量同步
原因:
- Slave offset 已不在 Master 的 backlog 中
- Master 的
repl-backlog-size太小
解决:
- 增加
repl-backlog-size(推荐 100MB - 1GB) - 检查网络是否频繁抖动
问题 3:数据不一致
原因:
- 使用了非幂等的命令(如
INCR、RPUSH)在主从切换时可能重复执行
解决:
- 使用幂等的业务逻辑
- 更严重的不一致,用哨兵 + 半同步方案
七、 复制的一些陷阱
7.1 写操作只能在 Master
# Slave 上默认是只读的127.0.0.1:6380>SET name"Tom"(error)READONLY You can'twriteagainst areadonly replica.# 除非修改配置(不推荐)replica-read-only no7.2 主从延迟导致数据不一致
# Master 写入Master: SET counter100# 立即在 Slave 读取(可能还没同步)Slave: GET counter# 可能还是 99解决方案:
- 关键读操作强制读 Master(通过代码控制)
- 使用
WAIT命令(阻塞直到数据同步到 N 个 Slave)
# Master 上执行,阻塞直到至少 2 个 Slave 确认同步127.0.0.1:6379>SET counter100OK127.0.0.1:6379>WAIT21000(integer)2# 2 个 Slave 在 1000ms 内确认7.3 主从切换时的数据丢失
如果 Master 宕机,Slave 被提升为新 Master,但旧 Master 上还有一些没同步到 Slave 的写命令,这些数据就永久丢失了。
解决方案:
- 配置
min-slaves-to-write和min-slaves-max-lag(至少 N 个 Slave 在线且延迟 < M 秒,Master 才接受写)
# redis.conf(Master) # 至少有 1 个 Slave,且延迟 ≤ 10 秒,才接受写 min-slaves-to-write 1 min-slaves-max-lag 10如果条件不满足,Master 会拒绝写请求,并返回错误。虽然降低了可用性,但保证了数据不丢。
八、 生产最佳实践
8.1 推荐配置
# Master 配置 requirepass YourStrongPassword repl-backlog-size 256mb # 足够大,避免部分同步退化 min-slaves-to-write 1 # 至少 1 个健康的 Slave min-slaves-max-lag 10# Slave 配置 replicaof 192.168.1.100 6379 masterauth YourStrongPassword replica-read-only yes replica-serve-stale-data yes repl-timeout 608.2 监控告警项
| 指标 | 告警阈值 | 处理 |
|---|---|---|
| master_link_status | down | 立即排查网络/防火墙 |
| lag | > 5 秒 | 检查 Slave 性能/网络 |
| connected_slaves | < 2(期望值) | 检查 Slave 是否掉线 |
| master_repl_offset - slave_repl_offset | > 1MB | backlog 可能太小 |
8.3 备份策略
# 在 Slave 上做备份(不占用 Master 资源)redis-cli-p6380BGSAVE# 每天定时备份 RDB 文件到远程服务器02* * *scp/var/lib/redis/dump.rdb backup@remote:/backup/redis-$(date+\%Y\%m\%d).rdb九、 面试高频题
Q1:主从复制的原理是什么?
A:分三个阶段:
- Slave 发送
PSYNC请求 - Master 执行
BGSAVE生成 RDB 并发送给 Slave(全量同步) - Master 把后续写命令持续发送给 Slave(增量同步)
- 断线重连后,如果 offset 在 backlog 中,执行部分同步;否则退化为全量同步
Q2:全量同步时,Slave 能提供服务吗?
A:
- 如果
replica-serve-stale-data = yes(默认),Slave 会返回旧数据 - 如果 = no,Slave 返回错误
Q3:主从复制是异步还是同步?
A:默认异步。Master 写完本地就返回客户端,不等待 Slave 确认。但可以用WAIT命令实现半同步。
Q4:主从延迟怎么解决?
A:
- 优化网络(同机房部署)
- 提升 Slave 配置(CPU/内存)
- 关键操作读 Master
- 使用
WAIT命令(但会降低性能)
Q5:怎么平滑地升级 Master?
A:
- 选一个 Slave,执行
REPLICAOF NO ONE提升为 Master - 把其他 Slave 指向新 Master
- 把旧 Master 降级为 Slave
十、 总结
主从复制是 Redis 高可用的基石,虽然它本身不能自动故障转移(那是哨兵的工作),但已经解决了 80% 的问题:
| 你的需求 | 是否用主从 |
|---|---|
| 提高读并发 | ✅ 一主多从 |
| 数据备份 | ✅ 从库做备份 |
| 读写分离 | ✅ 写主读从 |
| 自动故障转移 | ❌ 需要哨兵(下一期) |
| 海量数据存储 | ❌ 需要集群(下下期) |
最后的叮嘱:
- 主从不是越多越好,每个 Slave 都会增加 Master 的网络带宽压力
- 推荐:1 主 2 从 或 1 主 3 从,再多就用级联复制
- 生产环境务必配置密码(
requirepass+masterauth) - 定期检查复制延迟,这是很多事故的根源
下一期预告:Redis 哨兵模式——自动故障转移,让 Redis 永不宕机。
复制一时爽,监控要跟上。下期见!
