Redis分片集群实战:从槽位原理到扩容迁移与线上排障 Redis 分片集群这个题目乍一看像是老生常谈但真正在生产环境里踩过坑的人都知道单机 Redis 和哨兵模式解决的是高可用问题而分片集群解决的是容量与吞吐问题——这两件事的复杂度完全不在一个量级。我见过太多团队在 QPS 涨到几万、内存逼近单机上限时才匆忙上分片集群结果被槽位迁移、跨槽操作、客户端路由、热点倾斜这一连串问题打得措手不及。这篇内容就是把我这些年从零搭建、扩容、排障 Redis 分片集群的完整经验摊开来讲从为什么需要分片、槽位机制到底怎么运转到实际部署参数、客户端接入、扩容迁移的完整链路再到那些文档里不会写但线上一定会遇到的坑。不管你是刚接触 Redis 集群的新手还是正在为扩容发愁的运维老手都能从里面找到能直接抄作业的部分。1. 为什么单机 Redis 撑不住时要走分片这条路很多人对 Redis 扩容的第一反应是加内存、换更好的机器这个思路在数据量没到瓶颈时确实有效但一旦单实例内存超过 16GB 到 32GB 这个区间问题就开始集中爆发。我先把单机方案的天花板讲清楚你才能理解分片集群到底在解决什么。1.1 单机与主从架构的真实瓶颈在哪单机 Redis 的所有数据放在一个进程里主从复制虽然能分担读压力但写操作依然全部压在主节点上而且从节点要完整复制主节点的全量数据。这意味着你的总数据量永远受限于单机内存上限写吞吐也受限于单核处理能力——Redis 的命令执行是单线程的虽然网络 IO 用了多路复用但真正执行命令的那条线程只有一个。我实测过一个典型场景单实例在 8 核 32GB 的机器上简单 GET/SET 能跑到 8 万到 10 万 QPS但一旦涉及复杂命令比如 ZRANGE、HGETALL 大 hashQPS 会断崖式下跌到一两万。更麻烦的是持久化RDB 快照在数据量大时 fork 子进程会造成明显的内存抖动AOF 重写期间写放大也很可观。这些都不是换机器能根治的因为瓶颈在单线程模型和单机内存上限本身。主从加哨兵解决了主节点挂了自动切换的高可用问题但没解决容量和写吞吐的横向扩展问题。你加再多从节点写还是只能落在唯一的主节点上。所以当业务出现数据量持续增长、写入 QPS 持续攀升这两个特征时分片就是绕不开的选择。1.2 分片集群到底把什么问题拆开了分片集群的核心思路很朴素把整个数据集按某种规则切成 N 份每份放在不同的节点上每个节点只负责自己那一份数据的读写。这样总容量等于所有节点内存之和写吞吐也随节点数量线性增长。Redis 官方采用的是哈希槽hash slot方案总共 16384 个槽位每个 key 通过 CRC16 计算后对 16384 取模落到某个槽槽再分配给具体节点。为什么是 16384 而不是 65536这个数字不是随便定的。集群节点之间要互相交换槽位分配信息如果槽太多心跳包里的位图会很大浪费带宽16384 个槽在千级节点规模下已经够用位图大小也控制在 2KB心跳开销可接受。这个细节很多人不知道但理解它能帮你在设计 key 分布时有个数量级概念。分片带来的代价也很明确跨槽的多 key 操作不能直接用事务和 Lua 脚本要求所有 key 落在同一个槽客户端需要感知槽位路由。这些约束是后面所有踩坑的根源先记住这一点。1.3 什么规模该上分片什么规模还不用我的经验判断标准是这样的单实例内存使用率长期超过 60%、写入 QPS 稳定超过 5 万、或者业务明确预期半年内数据量翻倍这三个条件满足任意一个就该规划分片了。如果只是读多写少、数据量不大主从加读写分离完全够用上分片反而是给自己找麻烦。还有一个容易被忽略的点分片集群的最小生产规模建议是 3 主 3 从也就是 6 个节点。少于这个数主节点故障时的自动故障转移和槽位覆盖会有风险。所以如果你的数据量其实一个 16GB 实例就能装下硬上 6 节点集群是资源浪费。我见过有团队为了架构先进提前上集群结果运维复杂度翻倍收益却为零。2. 哈希槽机制分片集群运转的真正内核理解了为什么要分片接下来必须把槽位机制吃透因为集群的扩容、缩容、故障转移、客户端路由全部围绕槽位展开。这块原理不清楚后面所有操作都是盲人摸象。2.1 一个 key 从写入到落盘的完整路径当你执行SET user:1001:name 张三时客户端如果是集群感知的会先对 key 做 CRC16 校验得到一个大整数再对 16384 取模假设结果是 5461那么客户端就知道这个 key 属于 5461 号槽。接着客户端查本地缓存的槽位映射表发现 5461 号槽归节点 B 管于是把命令直接发给节点 B。如果客户端不是集群感知的把命令随便发给节点 A节点 A 一算发现这个槽不归自己会返回一个MOVED 5461 节点B地址的重定向响应客户端收到后要重新向节点 B 发起请求。这个重定向过程会多一次网络往返所以生产环境一定要用支持集群模式的客户端让它缓存槽位映射避免每次都重定向。这里有个关键细节槽位映射表在客户端是有缓存的当集群发生槽位迁移时客户端缓存会失效收到 MOVED 后更新缓存。如果迁移过程中访问的 key 正在搬家会收到ASK重定向这个和 MOVED 的区别在于 ASK 是临时性的客户端只对这一次请求做重定向不更新缓存。理解 MOVED 和 ASK 的区别是排查迁移期问题的关键。2.2 为什么跨槽操作会成为高频雷区集群模式下涉及多个 key 的命令必须保证所有 key 在同一个槽。比如MGET k1 k2 k3如果这三个 key 算出来的槽不一样直接报CROSSSLOT Keys in request dont hash to the same slot。事务 MULTI/EXEC 和 Lua 脚本同理脚本里访问的所有 key 都必须在同槽。解决办法是使用 hash tag。Redis 规定如果 key 里包含{...}那么只对花括号内的内容做哈希计算。比如{user1001}:name和{user1001}:age花括号里都是user1001算出来的槽一定相同就能放在同一个 MGET 或同一个 Lua 脚本里操作。这个技巧在实际业务里非常常用比如把同一个用户的所有字段用统一的 tag 包起来。但 hash tag 用过头会带来热点问题——如果所有 key 都用同一个 tag等于所有数据挤到一个槽一个节点上分片就白做了。所以 tag 的粒度要选在业务上需要原子操作的单元这个层级比如按用户 ID 做 tag而不是按业务大类做 tag。2.3 槽位分配与节点心跳的协作方式集群里每个节点都维护一份完整的槽位分配表节点之间通过 gossip 协议互相通信定期交换彼此的状态和槽位归属。每个节点每秒会向随机几个节点发 PING收到 PONG 后更新对方的状态。当某个节点被标记为疑似下线PFAIL后其他节点会通过 gossip 传播这个信息超过半数主节点确认后升级为 FAIL触发故障转移。故障转移时故障主节点的从节点会发起选举获得多数主节点投票后晋升为主节点接管原主节点的所有槽位。这个过程默认需要几秒到十几秒期间这部分槽位的请求会失败。所以集群模式下的客户端必须配置合理的重试策略否则故障转移窗口内会有大量报错。这里有个参数值得关注cluster-node-timeout默认 15000 毫秒。它决定了节点多久没响应就被判定为疑似下线。设太小会因网络抖动误判设太大会延长故障发现时间。我的经验是内网环境设 5000 到 8000 毫秒比较合适跨机房部署要适当放大。3. 从零搭一套可用的分片集群参数与步骤原理讲完进入实操。我用 6 个节点3 主 3 从的最小生产规模来演示端口规划为 7000 到 7005前三个是主节点候选后三个是从节点候选。这套配置可以直接复用到测试环境。3.1 节点配置文件的必改项与原因每个节点的配置文件里有几个参数是集群模式必须调整的我逐个说明为什么。# redis-7000.conf 示例 port 7000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 5000 appendonly yes appendfsync everysec protected-mode no bind 0.0.0.0cluster-enabled yes是开启集群模式的总开关不开这个后面所有集群命令都无效。cluster-config-file是节点自己维护的集群状态文件注意这个文件不要手动改由 Redis 自己管理节点重启时会读它恢复集群认知。cluster-node-timeout前面说过内网 5000 毫秒是个稳妥值。appendonly yes在集群模式下我强烈建议开启。虽然 AOF 有写放大但集群节点故障转移频繁RDB 快照间隔内的数据丢失在分片场景下影响面更大——丢的可能是一整个槽位区间的数据。appendfsync everysec是性能和安全的平衡点别用 always那会把写性能拖垮。注意protected-mode no和bind 0.0.0.0只在受控内网环境使用公网暴露的实例必须配合访问控制这是安全底线。3.2 用 cluster create 一键建集群的实际命令6 个节点都启动后用一条命令建集群redis-cli --cluster create \ 192.168.1.10:7000 192.168.1.10:7001 192.168.1.10:7002 \ 192.168.1.10:7003 192.168.1.10:7004 192.168.1.10:7005 \ --cluster-replicas 1--cluster-replicas 1表示每个主节点配一个从节点6 个节点正好 3 主 3 从。执行后 Redis 会自动分配槽位16384 个槽平均分给 3 个主节点每个约 5461 个。命令会提示你确认分配方案输入 yes 后开始握手、分配槽、设置主从关系。这里有个实操细节cluster create要求所有节点都是空数据状态如果节点里已有数据会拒绝执行。所以建集群前务必确认是全新实例或者先 FLUSHALL。我见过有人在有数据的实例上建集群失败折腾半天才发现是这个原因。建完后用redis-cli --cluster check 192.168.1.10:7000验证输出会显示槽位覆盖是否完整、主从关系是否正确、是否有节点处于异常状态。这个 check 命令是日常巡检的必备工具建议养成习惯。3.3 客户端接入时最容易配错的三个地方集群建好只是第一步客户端接入才是真正容易翻车的地方。以常见的 Java 客户端为例配置里有三个点必须注意。第一是节点列表不要只写一个。虽然客户端理论上连一个节点就能发现整个集群拓扑但如果这个节点恰好故障客户端启动时就连不上。建议把 3 个主节点地址都配上客户端会自动做拓扑发现。第二是超时和重试策略。集群模式下故障转移期间会有短暂不可用客户端要配置合理的重试次数和重试间隔。我一般设最大重试 3 次间隔 100 到 200 毫秒配合cluster-node-timeout能覆盖大部分转移窗口。第三是连接池大小。分片集群下客户端要和多个节点建连连接池要按节点数 × 每节点连接数来估算。如果按单机思路配一个小连接池集群下会出现连接不够用、请求排队的情况。我通常每个节点配 8 到 16 个连接起步再根据实际 QPS 调整。# 客户端配置示意 cluster: nodes: - 192.168.1.10:7000 - 192.168.1.10:7001 - 192.168.1.10:7002 max-redirects: 3 timeout: 2000 pool: max-total-per-node: 16max-redirects控制最多跟随几次重定向设太小在迁移期容易失败设太大可能掩盖拓扑异常3 次是个合理值。4. 扩容与槽位迁移分片集群最危险的操作集群跑起来之后最考验人的就是扩容。业务增长要加节点就得把现有槽位从老节点迁一部分到新节点这个过程涉及数据搬迁稍有不慎就会影响线上。4.1 加节点与重新分片的完整流程扩容分两步先把新节点加入集群再做槽位重新分配。假设要加两个新节点 7006 和 7007一个做主一个做从。# 加入主节点 redis-cli --cluster add-node 192.168.1.10:7006 192.168.1.10:7000 # 加入从节点指定跟随 7006 redis-cli --cluster add-node 192.168.1.10:7007 192.168.1.10:7000 \ --cluster-slave --cluster-master-id 7006的节点ID新节点加入时槽位是 0需要重新分片把槽迁过去redis-cli --cluster reshard 192.168.1.10:7000 \ --cluster-from 源节点ID \ --cluster-to 7006节点ID \ --cluster-slots 2000 \ --cluster-yes--cluster-slots 2000表示从源节点迁 2000 个槽到目标节点。这个数字要算如果原来 3 主各 5461 槽加一个主节点后理想是 4 主各 4096 槽那么每个老节点要迁出约 1365 槽。实际迁移时可以从每个老节点各迁一部分到新节点保证负载均衡。4.2 迁移过程中 ASK 重定向是怎么工作的槽位迁移不是瞬间完成的而是一个槽一个槽地搬。对于正在搬的槽源节点上的 key 会逐个 MIGRATE 到目标节点。在这个窗口内如果客户端访问的 key 还在源节点源节点正常返回如果 key 已经迁走源节点返回 ASK 重定向让客户端去目标节点取。关键点在于ASK 重定向是临时的客户端不会更新槽位缓存因为槽还没正式归属目标节点。只有当整个槽的所有 key 都迁完源节点才把槽的归属正式转给目标节点此后客户端再访问会收到 MOVED这时才更新缓存。这个设计保证了迁移期间数据不丢、请求不误路由。我踩过的一个坑是迁移大槽时如果 key 特别多MIGRATE 会分批进行期间源节点和目标节点都要处理正常请求CPU 和网络压力会明显上升。所以迁移一定要在低峰期做并且控制迁移速度。--cluster-pipeline参数可以控制每次批量迁移的 key 数量默认 10压力大时可以调小。4.3 迁移期的监控指标与回滚思路迁移期间要盯几个指标源节点和目标节点的 CPU 使用率、网络带宽、cluster_migration相关状态、客户端错误率。如果发现错误率飙升或节点负载过高要能及时暂停。暂停的办法是中断 reshard 命令CtrlC已经迁完的槽不会回退但未迁的槽保持原状集群仍可正常服务。这就是为什么迁移要分批做——一次迁太多出问题回滚成本高分批迁每批验证通过再继续风险可控。回滚的话如果某个槽迁错了节点可以用cluster setslot 槽号 node 目标节点ID手动纠正但前提是该槽的数据已经完整在目标节点上。所以迁移前做好数据校验很重要redis-cli --cluster check能帮你确认槽位覆盖和数据一致性。5. 线上真实踩过的坑与排查链路前面讲的都是应该怎么做这一节讲实际会怎么坏。这些坑我在不同项目里反复遇到每一个都有完整的排查过程你可以对照自己的环境提前规避。5.1 热点 key 导致单节点被打爆的定位过程有一次线上监控报警说集群里某个节点 CPU 打到 100%其他节点却很闲。第一反应是槽位分配不均但cluster check显示槽位是平均的。于是上redis-cli --hotkeys采样发现某个促销活动的商品详情 key 被疯狂访问而这个 key 恰好落在那台高负载节点上。根因很清楚槽位平均不代表流量平均。一个爆款商品的 key 可能带来几十万 QPS全压在一个槽一个节点上。解决办法有两个层面短期用本地缓存挡住热点读或者对这个 key 做副本分散比如 key 加随机后缀拆成多份读的时候随机选一份长期要在业务设计上避免单 key 过热比如把大 key 拆小、热点数据前置到应用层缓存。这个坑的教训是分片集群的负载均衡是槽位均衡不是流量均衡监控一定要看单节点的 QPS 和 CPU不能只看集群总量。5.2 大 key 迁移卡死集群的应急处理还有一次扩容迁移某个槽时卡住了cluster nodes显示迁移状态一直不结束客户端开始大量超时。查下来发现那个槽里有个超大 hash几十万个 fieldMIGRATE 单次搬不动反复重试把源节点堵死了。应急处理是先把迁移停掉然后用HSCAN分批把这个大 hash 拆成多个小 key再重新迁移。根治办法是在业务侧禁止产生大 key写入前做大小校验超过阈值比如单 value 超过 10KB、集合元素超过 5000就告警或拒绝。大 key 在分片集群里的危害比单机更大因为它不仅占内存还会在迁移、删除、持久化时造成阻塞。我现在的习惯是上线前用redis-cli --bigkeys扫一遍把大 key 扼杀在摇篮里。5.3 客户端缓存槽位过期引发的连环重定向某次故障转移后部分客户端出现持续的高延迟抓包发现大量 MOVED 重定向。原因是这些客户端用的版本较老槽位缓存更新不及时故障转移后还按旧映射发请求每次都要被重定向纠正。排查方法是看客户端日志里的重定向次数正常情况应该接近零如果持续有大量 MOVED说明缓存更新有问题。解决办法是升级客户端到支持集群拓扑自动刷新的版本或者在应用层监听拓扑变化事件主动刷新。这个坑提醒我客户端版本和集群模式的兼容性要在选型阶段就验证清楚别等出故障才发现。6. 让集群长期稳定的运维习惯集群搭好、扩容做完剩下的就是日常运维。这一节分享几个我坚持了很多年的习惯都是用小成本换大稳定的做法。6.1 日常巡检该看哪几个指标我每天的巡检清单固定看这几项cluster info里的cluster_state是否为 ok、cluster_known_nodes数量是否和预期一致、每个节点的内存使用率和连接数、慢查询日志里有没有集群相关命令。每周做一次cluster check确认槽位覆盖完整、没有孤立节点。这些指标里cluster_state是最直接的信号一旦变成 fail 说明有槽位不可用要立即介入。连接数突增往往意味着客户端重连风暴可能是某个节点抖动导致的。慢查询里如果出现 MIGRATE、KEYS 这类命令要警惕是不是有人在集群上做了危险操作。6.2 故障转移演练该怎么做才有效很多团队配了集群但从没演练过故障转移真出事时手忙脚乱。我的做法是每季度做一次演练选一个从节点手动CLUSTER FAILOVER触发主从切换观察切换耗时、客户端错误率、数据是否一致。演练要在业务低峰做并且提前通知相关方。演练的价值在于暴露配置问题。我就在演练中发现过客户端重试次数配少了切换窗口内报错率超标也发现过某个从节点数据落后太多切换后丢了部分数据。这些问题不演练永远发现不了。6.3 版本升级与参数调整的稳妥节奏集群升级比单机麻烦因为要滚动升级还要保证升级期间槽位可用。我的节奏是先升从节点观察稳定后再手动 failover 让从变主再升原来的主节点。每次只动一个节点升完验证cluster check通过再动下一个。参数调整也是同理cluster-node-timeout这类影响故障判定的参数改之前要评估网络环境改之后要观察一段时间。别一次性改多个参数出了问题无法定位是哪个改动导致的。这种小步慢跑的节奏看着慢但比出故障后救火快得多。最后分享一个我个人的体会分片集群的复杂度主要不在搭建而在长期运维中的各种边界情况。把槽位机制吃透、把监控做细、把演练做勤这三件事做到位集群就能稳稳当当跑很多年。那些看起来麻烦的巡检和演练恰恰是省下未来救火时间的投资。