Redis 主从、哨兵与集群
单机 Redis 有两个硬伤:容量受限于单机内存,故障时无法自动恢复。主从复制解决读扩展与数据冗余,哨兵解决自动故障转移,Cluster 解决水平分片。三者是递进关系,不是替代关系。本文以 Redis 7.x 为基准,讲清复制流程、哨兵选主、Cluster 的槽位机制,以及各自的失效边界。
一、主从复制
从节点执行 REPLICAOF 127.0.0.1 6379 后,复制分三个阶段。
第一阶段:建立连接与全量同步。 从节点发送 PSYNC ? -1,主节点执行 BGSAVE 生成 RDB,同时把期间的新写命令存入复制缓冲区。RDB 发送完毕后,主节点再补发缓冲区里的命令。RDB 传输期间,从节点用旧数据(或空数据)对外服务。
第二阶段:命令传播。 全量同步完成后,主节点每执行一条写命令就异步发给所有从节点。从节点维护 master_repl_offset,记录已处理的字节偏移。
第三阶段:部分重同步。 网络闪断重连时,从节点发送 PSYNC <runid> <offset>,如果主节点的复制积压缓冲区(repl backlog)里还保留着该偏移之后的数据,就只补发缺失部分,不必再做全量同步。积压缓冲区由 repl-backlog-size 控制,默认 1 MB,高写入场景建议调到 64 MB 以上。
repl-backlog-size 64mb
repl-diskless-sync yes
repl-diskless-sync-delay 5
repl-diskless-load on-empty-db
repl-diskless-sync yes 让主节点直接把 RDB 写入 socket,不落盘,适合磁盘慢但网络好的环境。repl-diskless-sync-delay 是等待窗口,让多个从节点同时连接时共享一次传输。
复制是异步的:主节点写完内存就返回,不等从节点确认。所以从节点可能落后主节点若干毫秒,故障切换时这部分数据会丢。可以用 WAIT numreplicas timeout 阻塞等待指定数量的从节点确认,但它只保证「命令已到达从节点」,不保证已执行,也不是强一致方案。
二、哨兵:自动故障转移
哨兵(Sentinel)是独立进程,默认端口 26379,职责有三:监控、通知、自动故障转移。
监控。 哨兵每秒向主从节点发送 PING,超过 down-after-milliseconds(默认 30 秒)无响应标记为主观下线(SDown)。
客观下线。 单个哨兵可能因网络问题误判,所以需要多个哨兵确认。当 quorum 个哨兵都认为主节点 SDown,才标记为客观下线(ODown)。哨兵集群建议至少 3 个、奇数个,部署在不同物理机。
选主。 由领头哨兵(通过 Raft 选举产生)从从节点中挑选新主,规则依次是:
- 过滤掉主观下线、断线时间过长的从节点;
- 按
replica-priority排序,值越小优先级越高,0 表示永不选为主; - 优先级相同,比较复制偏移量,越大(数据越新)越优先;
- 仍相同,比较
runid,字典序小的胜出。
选出新主后,领头哨兵向其余从节点发送 REPLICAOF 指向新主,并向客户端发布新拓扑。旧主恢复后会作为从节点接入。
sentinel monitor mymaster 10.0.0.11 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
quorum 设为 2 表示至少 2 个哨兵同意才判定客观下线;parallel-syncs 1 表示故障转移后一次只让一个从节点同步新主,避免新主被多个全量同步压垮。
三、脑裂与数据丢失
网络分区时可能出现:主节点与哨兵、从节点失联,但客户端仍能写入主节点;哨兵在另一侧把从节点提升为新主。分区恢复后,旧主降级为从节点,会清空自己的数据做全量同步,期间写入的数据全部丢失。
Redis 提供两个参数限制损失:
min-replicas-to-write 1
min-replicas-max-lag 10
主节点要求至少有 1 个从节点,且延迟不超过 10 秒,否则拒绝写入。这能减少脑裂期间写入的数据量,但无法完全避免。
四、Cluster:水平分片
单机内存有上限,哨兵只能解决高可用,不能解决容量。Cluster 把数据分散到多个主节点,每个主节点负责一部分槽位。
槽位机制。 整个键空间划分为 16384 个槽(slot),每个 key 通过 CRC16(key) mod 16384 映射到槽,槽再分配给主节点。客户端访问错误节点时,节点返回 MOVED 重定向;正在迁移的槽返回 ASK 重定向。
# 创建 3 主 3 从集群
redis-cli --cluster create \
10.0.0.11:6379 10.0.0.12:6379 10.0.0.13:6379 \
10.0.0.14:6379 10.0.0.15:6379 10.0.0.16:6379 \
--cluster-replicas 1
# 查看集群状态
redis-cli -c -p 6379 cluster info
redis-cli -c -p 6379 cluster nodes
cluster info 的关键字段:cluster_state:ok 表示所有槽都有节点负责;cluster_slots_assigned:16384 表示槽全部分配;cluster_known_nodes:6 是节点总数。
为什么是 16384。 心跳包会携带槽位位图,16384 位即 2 KB,节点间每秒交换多次,位图太大浪费带宽;而 16384 个主节点在现实中远超需求。集群规模建议不超过 1000 个节点。
Hash Tag。 需要多 key 原子操作的场景(如 MGET、事务、Lua)必须让 key 落在同一槽,用 {} 指定哈希标签:
SET {user:1001}:name "tom"
SET {user:1001}:age 18
# 两者都按 "user:1001" 计算槽位,落在同一节点
五、Cluster 的故障转移
Cluster 内部没有独立哨兵,故障检测与选主由节点自己完成:
- 节点间通过 gossip 协议每秒交换
PING/PONG; - 某节点被多数主节点标记为
PFAIL,超过cluster-node-timeout后升级为FAIL; - 故障主节点的从节点发起选举,获得多数主节点投票后晋升;
- 新主接管原主负责的槽位,并通过 gossip 广播拓扑变更。
cluster-require-full-coverage yes(默认)表示只要有一个槽不可用,整个集群拒绝服务。追求可用性可设为 no,让其他槽继续服务。
六、三种架构怎么选
| 架构 | 解决 | 不解决 | 适用 |
| --- | --- | --- | --- |
| 主从 | 读扩展、数据冗余 | 自动故障转移、容量 | 读多写少,可接受手动切换 |
| 哨兵 | 自动故障转移 | 容量、写扩展 | 数据量小于单机内存,要求高可用 |
| Cluster | 容量、写扩展、高可用 | 跨槽多 key 原子操作 | 数据量大、写入高 |
提示:Cluster 不支持多数据库(只有 db0),也不支持跨槽的
MGET、MSET、事务和 Lua 脚本。迁移前必须梳理现有代码中的多 key 操作。
常见坑 / 最佳实践
- 哨兵至少部署 3 个,且不要和 Redis 同机。 单哨兵或与主节点同机部署,在网络分区时无法形成多数派,故障转移会失效。
repl-backlog-size要按写入量估算。 默认 1 MB 在高写入下很快被覆盖,从节点重连只能全量同步,反复触发BGSAVE拖慢主库。- Cluster 的 key 必须规划 Hash Tag。 上线后再改 key 结构意味着数据迁移,成本极高,设计阶段就要把「同业务、需原子操作」的 key 放进同一 tag。
- 不要用
WAIT实现强一致。 它只保证命令送达从节点,不保证执行,主从切换时仍可能丢数据。 - 监控复制延迟。
INFO replication中的master_repl_offset与从节点slave_repl_offset之差是延迟字节数,持续增大说明从节点处理不过来。
小结
- 主从复制分全量同步、命令传播、部分重同步三阶段,部分重同步依赖复制积压缓冲区。
- 哨兵通过主观下线、客观下线、领头哨兵选主完成故障转移,选主顺序是优先级、偏移量、runid。
- 脑裂会导致数据丢失,
min-replicas-to-write只能减少损失,不能根除。 - Cluster 用 16384 个槽做分片,客户端靠
MOVED/ASK重定向,多 key 操作必须用 Hash Tag 保证同槽。 - 选型按数据量和可用性要求递进:主从做冗余,哨兵做高可用,Cluster 做容量与写扩展。
- 点赞
- 收藏
- 关注作者
评论(0)