考察点: 全量/增量同步、自动故障转移、slot分片、Gossip协议
🎬 开场:一个关于"餐厅"的故事
想象三种餐厅的运营模式:
模式1:单店经营(单机Redis) 🏪
只有1个厨师、1个店面
优点:简单直接
缺点:厨师生病了,餐厅就关门了!
模式2:总店+分店(主从复制) 🏪🏪
1个总店(主)+ 多个分店(从)
总店做菜,分店学习菜谱
优点:分店可以分担接客压力
缺点:总店倒闭了,分店不会做新菜
模式3:总店+分店+店长(哨兵) 🏪🏪👔
有个店长监督:
- 总店倒了,自动提拔一个分店当总店
- 其他分店跟着新总店学习
优点:自动故障转移
缺点:数据量大了,一个总店忙不过来
模式4:连锁餐厅集团(Redis Cluster) 🏪🏪🏪🏪
多个总店,每个负责部分菜系
- A店:川菜
- B店:粤菜
- C店:湘菜
优点:分布式,扩展性强
缺点:管理复杂
Redis的高可用方案就是这样的演进! 🚀
第一部分:主从复制(Replication)👥
1.1 什么是主从复制?
架构:
[Master] ← 主节点(读+写)
/ \
/ \
[Slave1] [Slave2] ← 从节点(只读)
| |
[Slave3] [Slave4] ← 二级从节点
核心思想:
- Master接收写操作,Slave复制Master的数据
- Slave提供读操作,分担Master压力
- Master宕机,手动切换
1.2 主从复制的实现原理 ⭐⭐⭐⭐⭐
第一步:建立连接
# 从节点配置
127.0.0.1:6380> REPLICAOF 127.0.0.1 6379
# 或在配置文件
replicaof 127.0.0.1 6379
第二步:全量同步(第一次)
时间线:
T1: Slave → Master: "PSYNC ? -1" (请求同步)
T2: Master → Slave: "FULLRESYNC <runid> <offset>"
T3: Master执行BGSAVE,生成RDB快照
T4: Master发送RDB文件给Slave
T5: Slave清空旧数据,加载RDB文件
T6: Master发送复制缓冲区的增量命令
T7: Slave执行增量命令
T8: 全量同步完成!
详细流程图:
Master Slave
| |
| <──── PSYNC ? -1 ──────── | (请求同步)
| |
├─ 判断是否首次同步 |
| ↓ 是 |
├─ 生成 runid 和 offset |
| |
| ──── FULLRESYNC ───────→ | (全量同步)
| |
├─ fork子进程 |
├─ 生成RDB快照 |
├─ 记录复制缓冲区 |
| (新写入的命令) |
| |
| ──── 发送RDB文件 ──────→ |
| ├─ 清空旧数据
| ├─ 加载RDB
| |
| ──── 发送增量命令 ─────→ |
| ├─ 执行命令
| |
✓ 同步完成 ✓
第三步:增量同步(后续)
正常运行期间:
Master执行命令 → 写入AOF → 发送给Slave → Slave执行
示例:
客户端 → Master: SET key1 "value1"
Master → Slave: *3\r\n$3\r\nSET\r\n...
Slave执行: SET key1 "value1"
实时同步!
第四步:部分重同步(断线重连)
场景:Slave断线后重新连接
Slave → Master: "PSYNC <runid> <offset>"
Master判断:
1. offset在复制积压缓冲区内?
├─ 是 → 发送缓冲区的增量数据(部分重同步)
└─ 否 → 全量同步(重新传RDB)
复制积压缓冲区:
┌────────────────────────────────┐
│ 默认1MB的环形缓冲区 │
│ [cmd1][cmd2][cmd3]...[cmdN] │
│ ↑ │
│ offset │
└────────────────────────────────┘
1.3 配置示例
Master配置(redis-master.conf)
# 端口
port 6379
# 绑定IP
bind 0.0.0.0
# 设置密码
requirepass master_password
# 持久化
appendonly yes
Slave配置(redis-slave.conf)
# 端口
port 6380
# 主节点地址
replicaof 127.0.0.1 6379
# 主节点密码
masterauth master_password
# 从节点只读
replica-read-only yes
# 复制积压缓冲区大小(建议设大一点)
repl-backlog-size 10mb
1.4 主从复制的优缺点
✅ 优点:
- 读写分离:Master写,Slave读,提高并发
- 数据备份:Slave是Master的备份
- 高可用基础:为哨兵和集群提供基础
❌ 缺点:
- 不能自动故障转移:Master宕机需要手动切换
- 复制延迟:网络延迟导致主从不一致
- 全量同步开销大:RDB生成和传输耗时
- 单点写入瓶颈:所有写操作都在Master
1.5 监控主从状态
# Master查看从节点
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=1000,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=1000,lag=0
# Slave查看主节点
127.0.0.1:6380> INFO replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_sync_in_progress:0
slave_repl_offset:1000
第二部分:Redis哨兵(Sentinel)🛡️
2.1 什么是哨兵?
哨兵 = 主从复制 + 自动故障转移
架构:
[Sentinel1] [Sentinel2] [Sentinel3] ← 哨兵集群(监控)
↓ ↓ ↓
[Master] ←─── 监控 ───→ [Master]
/ \
/ \
[Slave1] [Slave2]
哨兵的职责:
- 监控:检查Master和Slave是否正常运行
- 通知:节点故障时通知管理员
- 自动故障转移:Master宕机时,自动提升Slave为Master
- 配置提供者:客户端从Sentinel获取Master地址
2.2 哨兵的工作原理
1️⃣ 监控(Monitoring)
每个Sentinel每秒:
1. 向Master发送PING
2. 向所有Slave发送PING
3. 向其他Sentinel发送PING
检测结果:
- PONG:正常
- 超时:可能下线
2️⃣ 主观下线(Subjectively Down, SDOWN)
单个Sentinel判断:
Master在30秒内没回复PING → 主观下线
配置:
sentinel down-after-milliseconds mymaster 30000
3️⃣ 客观下线(Objectively Down, ODOWN)
多个Sentinel协商判断:
Sentinel1: Master下线了
Sentinel2: Master下线了
Sentinel3: Master正常
投票:2票下线 vs 1票正常
结果:达到quorum(法定人数)→ 客观下线
配置:
sentinel monitor mymaster 127.0.0.1 6379 2 # quorum=2
4️⃣ 选举Leader Sentinel
当Master客观下线后:
1. 各Sentinel投票选出Leader
2. Leader负责执行故障转移
3. 使用Raft算法
投票规则:
- 先到先得
- 每个Sentinel只能投一票
- 获得超过半数票的成为Leader
5️⃣ 故障转移(Failover)
Leader Sentinel执行:
步骤1:从Slave中选择新的Master
选择规则:
1. 排除下线的Slave
2. 排除5秒内没响应的Slave
3. 排除与旧Master断开超过10秒的Slave
4. 选择优先级最高的(replica-priority)
5. 选择复制偏移量最大的(数据最完整)
6. 选择runid最小的
步骤2:让选中的Slave执行SLAVEOF NO ONE → 变成Master
步骤3:让其他Slave执行SLAVEOF新Master
步骤4:将旧Master标记为Slave(恢复后自动变成从节点)
步骤5:通知客户端新的Master地址
流程图:
Master宕机
↓
[Sentinel1] 发现超时 → 主观下线
↓
询问其他Sentinel
↓
[Sentinel2] [Sentinel3] 也认为下线
↓
达到quorum → 客观下线
↓
选举Leader Sentinel
↓
Leader执行故障转移:
├─ 选择Slave1作为新Master
├─ Slave1: SLAVEOF NO ONE
├─ Slave2: SLAVEOF Slave1
└─ 通知客户端
↓
故障转移完成!
2.3 配置示例
Sentinel配置(sentinel.conf)
# 端口
port 26379
# 监控的Master
# sentinel monitor <master-name> <ip> <port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 主观下线时间(30秒)
sentinel down-after-milliseconds mymaster 30000
# 故障转移超时时间(3分钟)
sentinel failover-timeout mymaster 180000
# 同时复制新Master的Slave数量(避免雪崩)
sentinel parallel-syncs mymaster 1
# Master密码
sentinel auth-pass mymaster master_password
启动哨兵
# 方式1
redis-sentinel /path/to/sentinel.conf
# 方式2
redis-server /path/to/sentinel.conf --sentinel
# 至少启动3个哨兵(奇数个,避免脑裂)
redis-sentinel sentinel-26379.conf
redis-sentinel sentinel-26380.conf
redis-sentinel sentinel-26381.conf
2.4 客户端连接哨兵
from redis.sentinel import Sentinel
# 连接哨兵
sentinel = Sentinel([
('127.0.0.1', 26379),
('127.0.0.1', 26380),
('127.0.0.1', 26381)
], socket_timeout=0.1)
# 获取Master(写操作)
master = sentinel.master_for('mymaster', socket_timeout=0.1)
master.set('key1', 'value1')
# 获取Slave(读操作)
slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
value = slave.get('key1')
2.5 哨兵的优缺点
✅ 优点:
- 自动故障转移:Master宕机自动切换
- 高可用:多个哨兵互相监督
- 配置简单:相对集群更简单
❌ 缺点:
- 不能水平扩展:Master还是单点写入
- 主从切换有短暂不可用:切换期间写入失败
- 数据不分片:所有数据在Master
第三部分:Redis Cluster(集群)🏢
3.1 什么是Redis Cluster?
集群 = 分布式 + 自动故障转移
架构(3主3从):
[Master1] [Master2] [Master3]
slot 0-5460 slot 5461-10922 slot 10923-16383
↓ ↓ ↓
[Slave1] [Slave2] [Slave3]
核心特点:
- 数据分片:16384个slot,分散到各个Master
- 自动故障转移:Master宕机,Slave自动提升
- 水平扩展:增加节点即可扩容
- 去中心化:无需Sentinel,节点之间通信
3.2 数据分片机制(Slot槽位)
槽位计算
Redis Cluster有16384个槽位(0-16383)
计算公式:
slot = CRC16(key) % 16384
示例:
key = "user:1000"
CRC16("user:1000") = 31234
slot = 31234 % 16384 = 14850
查找:14850在哪个节点?
- Master1: 0-5460 → 不在
- Master2: 5461-10922 → 不在
- Master3: 10923-16383 → 在这!
结果:访问Master3
槽位分配
3个节点均分:
Master1: slot 0-5460 (5461个)
Master2: slot 5461-10922 (5461个)
Master3: slot 10923-16383(5462个)
6个节点均分:
Master1: slot 0-2730
Master2: slot 2731-5460
Master3: slot 5461-8191
Master4: slot 8192-10922
Master5: slot 10923-13652
Master6: slot 13653-16383
Hash Tag(批量操作)
问题:
MGET user:1000 user:1001 user:1002
可能分布在不同节点,无法批量操作!
解决:Hash Tag
key = "user:{1000}" → CRC16("{1000}")
key = "user:{1001}" → CRC16("{1001}")
规则:
只对{}中的内容计算hash
user:{group1}:1000 → hash("group1")
user:{group1}:1001 → hash("group1")
结果:这些key在同一个slot,可以批量操作!
3.3 集群通信(Gossip协议)
节点之间每秒交换信息:
Node1 → Node2: "我负责slot 0-5460,健康状态OK"
Node2 → Node3: "Node1负责0-5460,我负责5461-10922"
Node3 → Node1: "Node2健康,Master3宕机了!"
Gossip消息类型:
- MEET:邀请新节点加入
- PING:检测节点是否在线
- PONG:回复PING
- FAIL:标记节点下线
3.4 故障转移
Master3宕机:
1. 多个节点发现Master3超时
2. 标记Master3为PFAIL(主观下线)
3. 超过半数节点认为下线 → FAIL(客观下线)
4. Slave3自动提升为Master
5. 接管slot 10923-16383
6. 通知其他节点
7. 故障转移完成!
时间:
- 检测:15秒(default node-timeout)
- 选举:几秒
- 总计:20-30秒
3.5 配置和部署
集群配置(redis-cluster.conf)
# 端口(6个节点用不同端口)
port 7000
# 开启集群模式
cluster-enabled yes
# 集群配置文件(自动生成)
cluster-config-file nodes-7000.conf
# 节点超时时间
cluster-node-timeout 15000
# 持久化
appendonly yes
创建集群
# 启动6个节点
redis-server redis-7000.conf
redis-server redis-7001.conf
redis-server redis-7002.conf
redis-server redis-7003.conf
redis-server redis-7004.conf
redis-server redis-7005.conf
# 创建集群(Redis 5.0+)
redis-cli --cluster create \
127.0.0.1:7000 \
127.0.0.1:7001 \
127.0.0.1:7002 \
127.0.0.1:7003 \
127.0.0.1:7004 \
127.0.0.1:7005 \
--cluster-replicas 1 # 每个Master 1个Slave
# 输出:
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7003 to 127.0.0.1:7000
Adding replica 127.0.0.1:7004 to 127.0.0.1:7001
Adding replica 127.0.0.1:7005 to 127.0.0.1:7002
集群管理命令
# 查看集群信息
redis-cli -c -p 7000 CLUSTER INFO
# 查看集群节点
redis-cli -c -p 7000 CLUSTER NODES
# 添加节点
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
# 删除节点
redis-cli --cluster del-node 127.0.0.1:7000 <node-id>
# 重新分片
redis-cli --cluster reshard 127.0.0.1:7000
3.6 客户端使用
from rediscluster import RedisCluster
# 连接集群
nodes = [
{"host": "127.0.0.1", "port": "7000"},
{"host": "127.0.0.1", "port": "7001"},
{"host": "127.0.0.1", "port": "7002"},
]
rc = RedisCluster(startup_nodes=nodes, decode_responses=True)
# 使用(自动路由到正确的节点)
rc.set("user:1000", "Tom")
value = rc.get("user:1000")
# 批量操作(使用Hash Tag)
pipe = rc.pipeline()
pipe.set("user:{group1}:1000", "Tom")
pipe.set("user:{group1}:1001", "Jerry")
pipe.execute()
3.7 集群的优缺点
✅ 优点:
- 水平扩展:增加节点即可扩容
- 高可用:自动故障转移
- 高性能:数据分片,并行处理
- 无中心节点:去中心化架构
❌ 缺点:
- 不支持多数据库:只能用database 0
- 批量操作受限:需要使用Hash Tag
- 事务受限:跨slot的key不能在同一个事务
- 运维复杂:节点管理、数据迁移
第四部分:三种方案对比 🥊
4.1 全面对比表
| 维度 | 主从复制 | 哨兵 | 集群 |
|---|---|---|---|
| 架构 | 1主N从 | 1主N从+哨兵 | M主M从(分片) |
| 故障转移 | ❌ 手动 | ✅ 自动 | ✅ 自动 |
| 数据分片 | ❌ 不支持 | ❌ 不支持 | ✅ 支持 |
| 写入能力 | 单节点 | 单节点 | 多节点(扩展) |
| 读取能力 | 多节点 | 多节点 | 多节点 |
| 最大QPS | 10万 | 10万 | 100万+ |
| 最大内存 | 单机 | 单机 | 集群总和 |
| 复杂度 | ⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 适用场景 | 读多写少 | 中小型应用 | 大型分布式 |
4.2 场景选择
场景1:小型项目(QPS < 1万)
推荐:单机 或 主从复制
理由:
- 数据量小,单机足够
- 运维简单
- 成本低
架构:
[Master] → [Slave]
场景2:中型项目(QPS 1-10万)
推荐:哨兵模式
理由:
- 自动故障转移
- 高可用保证
- 配置简单
架构:
[Sentinel1] [Sentinel2] [Sentinel3]
↓ ↓ ↓
[Master] ← [Slave1] [Slave2]
场景3:大型项目(QPS > 10万,数据量 > 100GB)
推荐:Redis Cluster
理由:
- 水平扩展
- 数据分片
- 高性能
架构:
[M1+S1] [M2+S2] [M3+S3] [M4+S4]...
场景4:金融、交易系统
推荐:哨兵 + 持久化
理由:
- 数据不能丢
- 强一致性要求
- 不能水平扩展(事务要求)
配置:
- appendonly yes
- appendfsync always
- 哨兵监控
4.3 演进路径
创业初期:单机Redis
↓
业务增长:主从复制(读写分离)
↓
可用性要求:哨兵(自动故障转移)
↓
数据量暴涨:Redis Cluster(分布式)
第五部分:实战优化建议 💡
5.1 主从复制优化
# 1. 增大复制积压缓冲区(避免全量同步)
repl-backlog-size 100mb # 默认1MB太小
# 2. 无盘复制(网络快、磁盘慢时)
repl-diskless-sync yes
# 3. 从节点优先级(故障转移时选择)
replica-priority 100 # 越小优先级越高
# 4. 只读从节点(避免写入)
replica-read-only yes
5.2 哨兵优化
# 1. 合理设置超时时间
sentinel down-after-milliseconds mymaster 5000 # 5秒检测
# 2. 控制并发复制(避免雪崩)
sentinel parallel-syncs mymaster 1 # 一次一个
# 3. 设置足够的quorum
# 3个哨兵:quorum=2
# 5个哨兵:quorum=3
5.3 集群优化
# 1. 合理规划槽位
# 保证每个Master负载均衡
# 2. 使用Hash Tag
# 需要批量操作的key用相同tag
# 3. 监控slot分布
redis-cli --cluster check 127.0.0.1:7000
# 4. 定期重新平衡
redis-cli --cluster rebalance 127.0.0.1:7000
🎓 总结:高可用决策树
[需要高可用吗?]
/ \
否 是
↓ ↓
[单机] [数据量多大?]
/ \
< 100GB > 100GB
↓ ↓
[哨兵模式] [集群模式]
记忆口诀 🎵
主从复制读写分,
一主多从来分担。
写入主节点执行,
从节点学习来复制。
哨兵监控自动切,
Master宕机自提拔。
投票选举新领导,
高可用性它保障。
集群分片水平扩,
数据分散槽位中。
Gossip协议互通信,
海量数据它能扛!
单机用于小应用,
哨兵适合中等站。
集群支撑大流量,
按需选择最恰当!
面试要点 ⭐
- 主从复制原理:全量同步(RDB)+ 增量同步(命令)+ 部分重同步
- 哨兵职责:监控、通知、自动故障转移、配置提供
- 哨兵选举:Raft算法,超过半数票
- 集群分片:16384个slot,CRC16(key) % 16384
- 集群通信:Gossip协议,去中心化
- 故障转移:选择优先级高、数据完整的Slave
- 选择依据:数据量、QPS、可用性要求
最后总结:
Redis高可用就像军队编制 🎖️:
- 主从复制 = 班(1个班长+几个战士)
- 哨兵 = 连(有连长监督,自动换班长)
- 集群 = 军团(多个连协同作战)
记住:没有最好的架构,只有最合适的架构! 🎯
加油,Redis架构师!💪