适合人群:单机 Redis 用得飞起,但一想到”它挂了怎么办、数据大了怎么办”就没底的同学。
一句话主线:主从解决读扩展和数据冗余,哨兵解决自动故障转移,Cluster 解决容量与写入的水平扩展。
一、单机 Redis 的两个天花板
- 可用性:单点。进程挂了、机器坏了,缓存全灭,流量全部砸向数据库(缓存雪崩的最猛形态)
- 容量与写性能:单机内存上限就是数据上限;10 万 QPS 的写入天花板,一个实例再顶也顶不住
三套方案依次解题:主从 → 哨兵 → Cluster。
二、主从复制:一主多从,读写分离
配置一行
1 | # 在从节点上执行,或写进配置 |
数据流是单向的:主节点接收写命令,异步复制给所有从节点;读请求可以分摊给从节点。
全量同步:第一次连上时
- 从节点发送
PSYNC ? -1(我啥也没有) - 主节点回
+FULLRESYNC <replid> <offset>,开始BGSAVE生成 RDB - 主节点把 RDB 发给从节点,从节点清空自己后载入
- 这期间主节点的新写命令存在**复制积压缓冲区(repl_backlog)**里,载入完再补发给从节点
增量同步:断线重连时
从节点短暂断线后重连,带上自己已同步到的 offset:
- offset 还在 repl_backlog 范围内 → 只补发缺的那一段(部分重同步)
- 断太久、缓冲区已被覆盖 → 退化成全量同步(代价大,所以 backlog 默认 1MB,写入大的集群要调大)
两个必须知道的特性:
- 异步复制:主节点不等从节点确认就回客户端 OK——极端情况下主宕机会丢最后一小段数据(这也是上一篇分布式锁”主从切换丢锁”的根源)
- 从节点过期策略:从节点不会主动删过期 key,等主库的删除命令同步过来,保证主从一致
三、哨兵(Sentinel):自动故障转移
主从有个明显短板:主节点挂了,谁来把某个从节点提升为主? 人工半夜爬起来敲命令不现实。哨兵就是干这个的独立进程:
- 监控:持续 ping 主从节点
- 通知:出问题时通知运维/程序
- 自动故障转移:主挂了,自动完成”选新主 + 其他从库改挂 + 通知客户端”
- 配置中心:客户端先连哨兵问”现在谁是主”,而不是写死地址
故障转移的完整流程
假设部署 3 个哨兵(哨兵自身也要奇数个、至少 3 个,避免哨兵自己单点):
- 主观下线(sdown):哨兵 A ping 主节点,超时了——“我觉得它挂了”
- 客观下线(odown):A 问其他哨兵”你们也觉得挂了吗?”,达到 quorum(配置的法定数,如 2/3)才判定真挂——防止单个哨兵因自身网络抖动误判
- 选举 leader 哨兵:哨兵之间投票选出一名主持人(Raft 思想:先到先得的多数派投票)
- 挑选新主:按优先级(
replica-priority)→ 复制 offset 最新的 → runid 最小的。优先级 0 的从节点永远不会被选 - 执行切换:选中的从节点
SLAVEOF NO ONE升为主;其余从节点改挂新主;旧主回来后也变成新主的从 - 通知客户端:客户端通过订阅哨兵的频道感知主节点变更
一个最小的哨兵配置:
1 | # sentinel.conf |
Java 侧(Lettuce/Redisson)都原生支持哨兵模式,配置里写哨兵地址列表即可,客户端自动发现当前主节点。
主从 + 哨兵 = 高可用,但容量和写入仍是单主扛着——这就轮到 Cluster。
四、Cluster:分片,水平扩展的正解
核心设计:16384 个槽位(slot)
Redis Cluster 不按”节点数”切分数据,而是先把 key 空间切成 16384 个 slot,再把 slot 分配给各主节点:
1 | # key 落在哪个槽:对 key 做 CRC16 再取模 |
3 主节点的集群(官方最小规模要求 6 节点:3 主 3 从):
- 节点 A:slot 0 ~ 5460
- 节点 B:slot 5461 ~ 10922
- 节点 C:slot 10923 ~ 16383
面试必问:为什么是 16384? 两个原因:心跳包里要带节点负责的 slot 位图,16384 个 bit = 2KB,再大网络开销就浪费了(CRC16 本身能产生 65536 个值);同时官方建议集群不超过 1000 个主节点,16384 个槽足够分。
客户端怎么找到正确的节点:MOVED / ASK
客户端可以连任意节点,节点对每条命令做检查:
- key 属于自己 → 正常执行
- 不属于自己 → 回
MOVED <slot> <ip:port>,客户端重定向到正确节点(聪明的客户端会缓存 slot 映射表,直接打对节点) - 特殊场景迁移中(slot 正在从 A 搬到 B):先回
ASK,客户端去 B 上临时执行这一条,不改缓存映射
节点间通信:Gossip 协议
集群里的节点通过 Gossip(PING/PONG/MEET/FAIL 消息)互相交换状态:谁在线、谁挂了、slot 分布如何。没有中心化的协调节点,每个节点都是对等的。cluster-node-timeout(默认 15 秒)内多数节点都联系不上某节点,就把它标记 FAIL。
高可用内置:每个主节点挂从
Cluster 不需要单独的哨兵进程——每个主节点配一个从节点,某主挂了,集群里过半主节点同意后,它的从自动升级(故障转移内置在集群协议里)。
集群的代价(必知)
- 多 key 命令受限:
MSET、MGET、Lua 多 key 操作要求所有 key 在同一个 slot。跨 slot 的 key 用 hash tag 强制同槽:{user:1001}:name、{user:1001}:cart——大括号里的内容才参与 slot 计算 - 事务与 Lua 只能在单 key 空间内用
- 数据库只有 db0(单机 16 个 db 在集群里不存在)
- 运维复杂度上升:扩缩容要迁移 slot(
redis-cli --cluster reshard)
快速搭一个本地实验集群
1 | # 一键创建 3 主 3 从的本地集群(6 个端口 7000-7005) |
五、三种方案怎么选
| 维度 | 主从 | 哨兵 | Cluster |
|---|---|---|---|
| 解决什么 | 读扩展、数据冗余 | 自动故障转移 | 容量 + 写入的水平扩展 |
| 写能力 | 单主 | 单主 | 分片到多主 |
| 容量上限 | 单机内存 | 单机内存 | 多机内存之和 |
| 额外组件 | 无 | 哨兵进程 x>=3 | 无(协议内置) |
| 客户端复杂度 | 低 | 中(发现主节点) | 中高(slot 路由) |
选型路径:
- 数据量单机放得下(< 10GB 级)、纯缓存:主从 + 哨兵,简单成熟
- 数据量或写入吞吐超单机:Cluster
- 云上托管:阿里云 Tair、AWS ElastiCache 直接选带主从/集群的规格,故障转移都替你做了
系列总结
六篇走完,回头看 Redis 的知识地图:
- 01 数据结构:选对结构,性能差十倍
- 02 持久化:RDB 快照与 AOF 日志的权衡,混合持久化是生产答案
- 03 缓存三大问题:穿透/击穿/雪崩对症下药,一致性用 Cache Aside
- 04 过期与淘汰:惰性 + 定期删除,内存满了按 LRU/LFU 淘汰
- 05 分布式锁:从 SETNX 到 Redisson 看门狗的三次进化
- 06 高可用:主从 → 哨兵 → Cluster 的三级火箭
Redis 的所有设计都围绕同一组矛盾:内存很快但有限、单线程很快但怕慢命令、复制提高可用性但引入一致性窗口。理解了这三组矛盾,Redis 的面试题和线上事故,你都能顺着推出答案。