Redis Cluster 集群实战:哈希槽、请求路由与故障转移全解析 Redis 集群这块东西我最早接触是在一个缓存命中率死活上不去、单节点内存天天告警的项目里。那时候团队的第一反应是“加机器、上主从”结果主从是配上了写压力还是全压在 master 上扩容也只是读扩展写瓶颈一点没动。后来才真正把 Cluster 这套分片机制吃透才发现很多人对它的理解停留在“把数据切开存到多个节点”这一层而真正决定它好不好用的是哈希槽怎么分、请求怎么路由、故障怎么转移、以及客户端到底懂不懂重定向。这篇就把我这些年踩过的坑、调过的参数、排过的故障按一个能直接抄作业的思路完整讲一遍。1. 先搞清楚 Cluster 到底解决什么问题1.1 单机与主从的天花板在哪很多人上手 Redis 的第一套高可用方案就是主从加哨兵。这套东西解决的是“读扩展”和“故障自动切换”但它有一个绕不过去的硬伤所有写请求最终都落到同一个 master 上。你加再多从节点写的吞吐和单机内存上限还是被那一台机器卡死。我见过一个场景业务方把几千万个会话 key 全塞进一个实例内存冲到 60 多 G主从复制延迟一度飙到十几秒哨兵切换的时候直接丢了一批写请求。Cluster 的思路完全不同。它把整个数据集按16384 个哈希槽hash slot切开每个节点负责一部分槽位写请求根据 key 落到对应槽的节点上。这样一来写压力被分散到多个 master内存上限也随节点数线性增长。你可以理解为主从是“一个老板带几个助理”助理只能帮忙看文件不能签字Cluster 是“几个老板各管一摊业务”每个人都能独立收单。1.2 哈希槽机制的核心逻辑Cluster 没有用一致性哈希而是用了固定 16384 个槽的取模方案。计算方式是slot CRC16(key) mod 16384为什么是 16384 而不是 65536这是官方权衡后的结果。槽位数量直接决定集群元数据的大小每个节点都要维护一份“哪个槽属于哪个节点”的映射表。16384 个槽用 bitmap 表示大约 2KB节点间心跳包能塞得下如果换成 65536心跳包体积会明显变大网络开销和 gossip 传播成本都上去了。这个数字不是随便定的是吞吐和元数据开销之间的平衡点。槽的分配是集群初始化时决定的。三个 master 的典型分法是 0-5460、5461-10922、10923-16383均分 16384。你可以手动调整比如把热点槽集中到性能更好的机器上但要注意每个节点至少要有一个槽否则它不参与集群投票。1.3 什么场景该上 Cluster什么场景别碰Cluster 不是银弹。我总结下来数据量大、写并发高、能接受一定运维复杂度的场景适合上而事务多、多 key 操作频繁、对延迟极度敏感的场景要慎重。具体来说如果你的业务里有大量MGET、MSET、SUNION这类跨 key 操作Cluster 会因为 key 不在同一个槽而直接报CROSSSLOT错误。解决办法是用hash tag比如user:{1001}:name和user:{1001}:age大括号里的内容相同CRC16 计算时就只算大括号内的部分保证它们落在同一个槽。但这又带来一个新问题如果大量 key 用了同一个 tag它们会全挤到一个节点上分片就失去意义了。所以 hash tag 要克制使用只给真正需要原子操作的 key 组加。2. 集群搭建从零到可用的完整流程2.1 节点规划与端口约定Cluster 模式下每个节点要开两个端口一个是给客户端用的服务端口比如 7000另一个是集群总线端口服务端口 10000即 17000用于节点间 gossip 通信、故障检测、配置传播。这个 10000 的偏移量是硬约定防火墙规则千万别只放服务端口否则节点之间握手都握不上集群永远处于fail状态。我一般建议至少 3 主 3 从也就是 6 个节点起步。为什么是 3 主因为集群的故障转移需要多数派投票3 个 master 里挂 1 个还能选出新主挂 2 个整个集群就不可用了。从节点数量看你的读扩展需求但每个主至少配 1 个从否则主挂了没有候选者接管。节点规划表大致长这样节点角色服务端口总线端口说明master-1700017000负责槽 0-5460master-2700117001负责槽 5461-10922master-3700217002负责槽 10923-16383slave-1700317003复制 master-1slave-2700417004复制 master-2slave-3700517005复制 master-32.2 配置文件的关键参数每个节点的配置文件里Cluster 相关的参数必须显式打开否则节点启动后是普通单机模式。核心配置如下port 7000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 15000 appendonly yes这里有几个参数值得展开说。cluster-config-file是节点自己维护的集群状态文件不要手动改它节点重启时会读这个文件恢复集群拓扑手改容易把集群搞乱。cluster-node-timeout是故障判定超时默认 15000 毫秒意思是节点间心跳超过这个时间没响应就标记为疑似下线PFAIL。这个值调太小会误判网络抖一下就触发故障转移调太大故障恢复慢。内网环境 15000 比较稳跨机房部署要适当加大。appendonly yes不是 Cluster 专属但在集群里我更推荐开 AOF。因为故障转移时从节点提升为主如果只靠 RDB最后一次快照之后的数据就丢了。AOF 的 everysec 策略最多丢 1 秒数据对大多数业务可接受。2.3 用 redis-cli 组建集群配置写好、节点都启动之后用redis-cli --cluster create一条命令就能组集群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--cluster-replicas 1表示每个主配一个从。命令执行后redis-cli 会自动分配槽位、指定主从关系然后打印一份分配方案让你确认。确认后集群就建好了。注意这条命令里的节点顺序有讲究。前三个会被优先选为 master后三个作为 slave 分配。如果你把顺序打乱可能出现主从交叉分配不符合预期的情况。建议按“先所有 master再所有 slave”的顺序写。组好之后用redis-cli --cluster check 127.0.0.1:7000验证输出里会显示每个节点的槽位范围、主从关系和集群状态。看到[OK] All 16384 slots covered才算真正可用。2.4 动态扩缩容加节点与迁槽业务增长要加节点时流程分两步先把新节点加入集群再迁移槽位过去。# 加入一个空 master redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 # 从已有节点迁 1000 个槽到新节点 redis-cli --cluster reshard 127.0.0.1:7000 \ --cluster-from source-node-id \ --cluster-to new-node-id \ --cluster-slots 1000 \ --cluster-yesreshard 的过程是在线迁移槽里的 key 会逐个搬到目标节点。迁移期间客户端访问正在迁移的槽如果 key 还在源节点源节点会返回ASK重定向如果已经搬走返回MOVED。这个过程对业务基本透明但迁移大批量 key 时会占用网络和 CPU建议放在低峰期做。缩容就是反过来先把要下线节点的槽全部迁走再用del-node移除。redis-cli --cluster del-node 127.0.0.1:7000 node-id实操心得迁槽之前一定要确认目标节点内存够用。我踩过一次坑迁了 2000 个槽过去结果目标节点内存直接打满触发 OOMkey 被淘汰了一批。迁槽前用redis-cli --cluster check看下各节点的 key 分布和内存占用心里有数再动手。3. 请求路由与重定向客户端到底经历了什么3.1 MOVED 与 ASK 的区别这是 Cluster 里最容易混淆的一对概念但理解它们对排查问题至关重要。MOVED表示“这个槽永久归另一个节点管你以后直接找它”。客户端收到 MOVED 后应该更新本地的槽位映射表下次同样的 key 直接发到正确节点。ASK表示“这个槽正在迁移中这个 key 现在在目标节点但槽的归属还没变”。客户端收到 ASK 后这次请求要转发到目标节点但不能更新本地映射表因为迁移完成后槽的归属才正式变更。打个比方MOVED 像是“我搬家了以后寄这个地址”ASK 像是“这个东西暂时放在邻居家你去他那拿但我还没正式搬”。3.2 智能客户端 vs 普通客户端普通客户端比如没开集群模式的 Jedis 单机版每次遇到重定向都要多一次网络往返性能很差。智能客户端会在本地缓存一份槽位映射表直接算好 key 该去哪个节点只有映射表过期时才触发重定向更新。主流客户端的集群支持情况客户端集群支持本地缓存备注Jedis支持有需用 JedisClusterLettuce支持有基于 Netty异步性能好redis-py支持有用 RedisCluster 类go-redis支持有生产环境常用选客户端的核心看两点是否维护本地槽位缓存、是否支持拓扑自动刷新。没有本地缓存的客户端每次请求都可能多一跳QPS 高的时候延迟会明显放大。3.3 重定向风暴的成因与规避重定向风暴是指大量请求同时收到 MOVED客户端频繁刷新映射表导致请求在节点间来回弹。常见诱因有两个一是集群刚做完 reshard槽位映射大面积变更二是客户端缓存刷新策略太激进一收到 MOVED 就全量拉取拓扑。规避办法客户端配置里把拓扑刷新间隔设一个合理值比如 30 秒到 1 分钟不要每次 MOVED 都全量刷新。另外 reshard 尽量小批量、分多次做别一次性迁几千个槽。4. 故障转移与一致性取舍4.1 故障检测的完整链路一个 master 挂掉到新 master 接管中间经历了好几个阶段。首先是节点间心跳超时其他节点把它标记为PFAIL疑似下线。当超过半数 master 都标记它为 PFAIL就升级为FAIL确认下线。这时它的从节点发起选举请求其他 master 投票拿到多数票后提升为新 master接管原主的槽位并广播新的拓扑。整个过程受cluster-node-timeout控制默认配置下大概十几秒完成。想加快可以调小超时但要权衡误判风险。4.2 脑裂与数据丢失的边界Cluster 用的是异步复制主节点写完就返回成功不等从节点确认。这意味着主挂掉的瞬间还没同步到从节点的写请求就丢了。这是 Cluster 为了性能做的取舍无法完全避免。更麻烦的是脑裂网络分区导致一部分节点以为主挂了、选出新主另一部分节点还认为老主活着。等网络恢复两个主可能都写过数据合并时就会冲突。Cluster 的应对是少数派分区停止服务如果某个分区的 master 数量不过半它拒绝写请求避免产生分叉数据。这个机制叫cluster-require-full-coverage相关的一致性保护理解它有助于你在网络抖动时判断集群行为是否符合预期。4.3 手动故障转移与运维操作有时候需要主动把主切到从比如给主节点做维护。用CLUSTER FAILOVER命令在从节点上执行# 在从节点上执行强制提升为主 redis-cli -p 7003 cluster failover默认是强制模式会直接切换可能丢少量数据。加TAKEOVER参数更激进连主节点是否同意都不问。生产环境做计划内维护建议用默认模式让主从先做一次数据同步再切减少丢失。注意事项手动 failover 之前确认从节点的复制偏移量和主节点差距不大。用INFO replication看master_repl_offset和slave_repl_offset差距越小切换越安全。5. 常见故障排查速查5.1 集群状态异常排查表现象可能原因排查命令解决方向cluster_state:fail有槽未分配cluster info检查槽覆盖重新分配节点一直握手失败总线端口不通telnet 总线端口放通防火墙大量 MOVED槽位映射过期cluster nodes客户端刷新拓扑CROSSSLOT 错误多 key 不同槽检查 key 命名用 hash tag主从频繁切换网络抖动看日志 PFAIL调大 node-timeout5.2 几个我踩过的真实坑第一个坑是总线端口没放通。当时服务端口都通了节点也能启动但cluster info一直显示fail折腾半天才发现是 17000 系列端口被防火墙拦了。集群节点之间握不上手永远组不成集群。第二个坑是hash tag 用过头。有个业务为了图省事把所有 key 都加了同一个 tag结果全落在一个节点上分片形同虚设那个节点内存直接爆掉。hash tag 是给需要原子操作的小 key 组用的不是全局方案。第三个坑是客户端没开集群模式。用单机客户端连集群遇到 MOVED 不会自动跳转直接报错。这个错误信息其实很明确但新手容易忽略以为是集群本身有问题。5.3 性能调优的几个抓手Cluster 的性能瓶颈通常不在分片本身而在跨节点操作和大 key。几个调优方向避免跨槽的多 key 命令能用 pipeline 就用 pipeline 减少往返大 key 拆分一个几 MB 的 hash 拆成多个小 hash避免单节点阻塞合理设置cluster-node-timeout内网 15 秒跨机房适当加大监控每个节点的内存和 QPS避免槽位分配不均导致热点6. 我个人的一些实操体会Cluster 这套东西配置本身不难难的是理解它的边界。它不是把单机 Redis 简单复制几份而是引入了一套分布式的槽位管理和故障转移机制代价就是跨槽操作受限、事务支持弱化、运维复杂度上升。我的建议是如果你的数据量在单机内存能扛住的范围内写 QPS 也没到瓶颈别急着上 Cluster主从加哨兵足够用运维简单得多。真到了单机扛不住的时候再上而且要提前把客户端选型、hash tag 规范、监控告警这些配套做好否则集群建起来容易稳定跑起来难。最后分享一个小技巧集群上线前用redis-cli --cluster check和--cluster info各跑一遍把槽位分布、主从关系、内存占用都截图存档。后面出问题的时候有这份基线数据对比排查效率会高很多。这个习惯我保持了几年救过好几次场。