面试考察点
- 是否理解 16384 个哈希槽与节点映射。
- 能否区分 MOVED 和 ASK 重定向。
- 是否知道高可用不能消除异步复制的数据风险。
核心答案
Redis Cluster 把键映射到 16384 个槽,再把槽分配给主节点。客户端根据槽路由请求,拓扑变化时通过 MOVED 更新长期映射;槽迁移过程中可能通过 ASK 临时访问目标节点。每个主节点可配置从节点参与故障转移。
集群使用异步复制,主节点故障时仍可能丢失尚未复制的数据,因此它提供高可用而不是强一致。
槽与多键操作
Key 的槽由 CRC16 计算;{} hash tag 可让相关 Key 落在同一槽,从而支持特定多键命令或 Lua 脚本。但过度使用同一 tag 会造成数据和流量倾斜。
槽路由的实际过程
客户端对 key 计算槽号,查询本地缓存的“槽 -> 主节点”拓扑,直接向对应节点发送命令。若拓扑已变化:
MOVED slot host:port表示该槽已稳定归属新节点,客户端应更新槽映射并重试。ASK slot host:port常出现在迁移窗口,客户端临时向目标节点发送 ASKING 后重试,但不应永久更新映射。
使用 Cluster 时需要支持重定向和拓扑刷新。把普通单机 Redis 客户端直接连到一个 Cluster 节点,可能在跨槽访问时反复失败或性能异常。
主从与故障转移边界
每个槽由一个主节点提供写服务,从节点异步复制。集群节点通过 gossip 交换健康和槽信息,检测到主节点故障后,由从节点发起选举成为新主。复制是异步的:客户端刚收到写成功,主节点尚未来得及把日志复制给从节点就故障,该写入仍可能丢失。
要根据 RPO/RTO 决定副本数、故障域、写确认策略和是否允许在副本不足时继续写。Redis Cluster 提高可用性,不等同于关系数据库的强一致复制。
多 Key 与 hash tag 设计
cart:{user:1001}:items
cart:{user:1001}:meta
花括号内相同部分决定槽,两个 key 可以在同一节点执行 Lua 脚本或事务。不要把所有 key 都写成 {global},那会让整个集群退化为一个热点槽。hash tag 应只用于确实需要原子处理的一小组相关数据。
跨槽的 MGET、事务和 Lua 通常受限制。应用需要拆成多个请求再在上层合并,或重新建模,让必须原子处理的数据天然共享同一槽。
扩缩容与迁移预案
迁移一个槽需要把该槽 key 逐步从源主节点搬到目标主节点,期间会产生 ASK 重定向。大 Key、热点 Key、网络带宽和 AOF/RDB 压力会影响迁移时间。生产操作前应:
- 检查每节点槽数、内存、CPU、复制延迟和大 Key。
- 小批量迁移并观察命令延迟、重定向率和客户端错误。
- 确认新节点副本和故障域分布,留出故障时的接管容量。
- 完成后验证槽覆盖、读写一致性和客户端拓扑刷新。
缩容比扩容更危险,因为目标节点要承接全部数据与流量;不能只看平均内存,还要看热点和故障冗余。
常见线上故障
集群状态为 fail 可能是槽未完全覆盖、多个主节点不可达或网络分区;频繁 MOVED 可能是客户端没有刷新拓扑;某个节点 CPU 高而其他节点空闲往往是 Key 分布或热点问题,而非简单“集群失效”。诊断应从槽、Key 和命令维度下钻。
扩缩容
扩容的本质是把一部分槽及其 Key 从旧节点迁移到新节点,期间客户端必须正确处理重定向。操作前评估大 Key、热点、网络和持久化压力,并分批执行和校验。
常见误区
Cluster 不代理所有请求,智能客户端需要维护拓扑。多数派故障检测与故障转移提高可用性,但网络分区、复制延迟和同时故障仍需纳入 RPO/RTO 设计。
高频追问与参考回答
追问:为什么槽数不是节点数?
槽作为稳定的逻辑分片层,把数据映射与物理节点解耦,扩缩容只需迁移部分槽,不必为每个 Key 重新定义节点规则。
追问:Cluster 能保证读到主库最新数据吗?
从副本读取时可能受到复制延迟影响;读取主节点通常更接近最新已处理写,但网络、故障和客户端重试语义仍要按业务要求设计。
追问:为什么集群节点数增加后单个热 Key 还是慢?
一个 Key 不会拆到多个槽,所有命令仍由一个主节点串行处理。需要本地缓存、请求合并、读副本、Key 副本或业务分片解决。
追问:迁移时业务需要停机吗?
正常槽迁移可在线进行,客户端正确处理 ASK/MOVED 即可;但大规模迁移和资源紧张时仍可能影响延迟,应限速、灰度并准备回退。
总结
Cluster 用槽实现水平分片,用副本和选举实现故障转移;客户端路由、迁移和一致性边界同样是设计重点。
机制全景图
下面把「Redis Cluster 的分片和故障转移原理是什么?」从输入到结果压缩成一条可复述的主链路。面试时先用图建立全局坐标,再进入局部实现,能避免只背零散结论。
flowchart LR
A["对 Key 计算 CRC16"]
A --> B["映射到 16384 槽"]
B --> C["客户端路由到节点"]
C --> D["MOVED/ASK 重定向"]
D --> E["迁移槽并恢复副本"]
完整链路:从输入到结果
沿着「对 Key 计算 CRC16 → 映射到 16384 槽 → 客户端路由到节点 → MOVED/ASK 重定向 → 迁移槽并恢复副本」观察输入、状态与输出,下面每个阶段都对应一个可以在源码、日志或系统表中验证的位置。
1. 对 Key 计算 CRC16
Cluster 以 hash slot 而非节点数直接映射,槽作为扩容迁移的最小路由单位。
2. 映射到 16384 槽
普通 Key 通过 CRC16%16384 定位;hash tag 可让多个 Key 落同槽以支持多键命令。
3. 客户端路由到节点
客户端缓存槽位拓扑并直连目标主节点,拓扑变化时更新映射。
4. MOVED/ASK 重定向
MOVED 表示稳定归属改变,ASK 表示迁移中的临时访问,客户端处理方式不同。
5. 迁移槽并恢复副本
reshard 在节点间迁移槽内 Key,故障转移由副本晋升,但少数写入仍受异步复制窗口影响。
源码与实现定位
| 入口 | 阅读重点 |
|---|---|
| src/cluster.c keyHashSlot | CRC16 与 hash tag |
| CLUSTER SLOTS/SHARDS | 槽位、节点与迁移状态 |
源码或系统表应按上表顺序追踪:先确认入口实际走到哪条路径,再用运行时数据验证,而不是仅凭类名或配置推测。
参数配置与可复现实验
redis-cli --cluster check host:6379
redis-cli CLUSTER SHARDS
统计每槽 Key/QPS,迁移含 Big Key 的槽并注入 MOVED/ASK,验证客户端拓扑刷新和延迟。
验证步骤与预期结果
1. 固定输入和基线
先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「最大/平均槽QPS」为主基线,记录值应满足「<2」;同时保存 各槽 Key/QPS 分布、cluster_state,使后续变化能够回到同一时间轴比较。
2. 从实现入口确认路径
在「src/cluster.c keyHashSlot」确认请求确实进入「CRC16 与 hash tag」对应的实现,再沿「CLUSTER SLOTS/SHARDS」观察「槽位、节点与迁移状态」。如果入口路径都未命中,就不应继续调整下游参数,而应先检查调用条件、版本或路由是否与假设一致。
3. 注入本文特有的失败模式
优先复现「全局 hash tag 制造单槽热点」,并把单一变量逐级放大,直到「最大/平均槽QPS」越过「>3」。随后再分别验证「客户端不处理 ASK/MOVED」和「槽迁移时执行大 Key 阻塞」,三类故障分开执行,避免多个变量同时变化而无法归因。
4. 执行止损和根因修复
第一轮只应用「移除全局 hash tag」,确认它能控制影响范围;第二轮应用「客户端正确处理 MOVED/ASK」,验证核心链路恢复;最后落实「迁移前治理 Big Key」,消除同类问题再次出现的条件。每一步都保留变更前后数据,不用“感觉变快了”替代测量。
5. 通过退出条件
实验只有同时满足三项才算通过:「最大/平均槽QPS」回到「<2」、「重定向率」回到「稳态接近0」、「迁移 P99」回到「预算内」,并且业务结果差异为零。若性能恢复但结果不一致,仍应视为失败;若指标恢复后很快再次越线,则说明只完成了临时止损,没有消除根因。
量化基线
| 指标 | 样例基线/口径 | 风险线 | 结论 |
|---|---|---|---|
| 最大/平均槽QPS | <2 | >3 | 槽热点 |
| 重定向率 | 稳态接近0 | 持续增长 | 拓扑缓存旧 |
| 迁移 P99 | 预算内 | Big Key 长尾 | 先拆 Key |
这些数值是实验口径或示例告警线,不是可复制到所有系统的固定答案;上线阈值应由本系统稳态、峰值和故障演练共同确定。
事故复盘:使用 hash tag 后单节点过热
为支持批量 Lua,团队把所有租户 Key 都写成 {global}:xxx,结果全部落同一槽,集群形同单节点。hash tag 应只绑定确需原子操作的小范围业务实体,而不是全局命名空间。
| 失败模式 | 首要证据 | 第一处置动作 |
|---|---|---|
| 全局 hash tag 制造单槽热点 | 各槽 Key/QPS 分布 | 移除全局 hash tag |
| 客户端不处理 ASK/MOVED | cluster_state | 客户端正确处理 MOVED/ASK |
| 槽迁移时执行大 Key 阻塞 | 重定向次数 | 迁移前治理 Big Key |
发布与回滚检查点
- 发布前:确认「src/cluster.c keyHashSlot」对应实现和上述配置在目标版本仍然有效,并保存「最大/平均槽QPS」基线。
- 灰度中:同时观察 各槽 Key/QPS 分布、cluster_state、重定向次数;任一指标越过表中风险线,就停止继续扩量。
- 回滚时:先执行「移除全局 hash tag」控制影响,再回退代码或参数;涉及持久状态时必须额外核对结果差异。
- 发布后:至少覆盖一个完整峰值周期,确认「全局 hash tag 制造单槽热点」没有再次出现,才关闭变更观察窗口。
方案对比与选型
| 方案 | 更适合的场景 | 主要收益 | 代价与边界 |
|---|---|---|---|
| Cluster | 数据量和吞吐需水平扩展 | 原生分片与故障转移 | 跨槽多键限制、客户端更复杂 |
| 客户端一致性哈希 | 可控分片中间层 | 规则灵活 | 迁移和高可用需自行实现 |
| 单实例主从 | 容量足够且操作依赖多键 | 语义简单 | 垂直容量上限明显 |
选型至少带上 Key 数量、Value 大小、命令复杂度、热点分布和过期速率,并用上面的量化基线验证;未知数据应明确为待测假设。
设计边界与工程取舍
Cluster 扩展的是多 Key 总吞吐,无法拆分一个热 Key;跨槽原子性和强一致也不会因增加节点自动获得。
工程落地遵循:Redis 是高性能数据结构服务,不应被当成没有容量边界的黑盒。回答时直接引用「src/cluster.c keyHashSlot」、配置实验和事故数据,比复述固定模板更有说服力。