资讯动态

Redis Cluster实战:从槽位分片到故障转移的完整指南

发布时间:2026/10/23 1:29:22 来源:尧图企业网站定制
1. 从哨兵到集群为什么我最后还是放弃了主从架构说实话我最早接触缓存的场景和大多数人一样一台Redis实例跑得好好的再挂一台从库做备份加上哨兵实现故障自动切换这就是当年标准的高可用三件套。但等你真的把业务体量做上去比如日活几十万、几百万单实例的写能力就开始吃力了。CPU成为瓶颈内存扩容代价又高哨兵架构再怎么折腾本质上仍是单主写。所有客户端往同一台主节点写写性能的上限被钉死在那里。Redis Cluster的出现解决的不是可用性问题——哨兵集群已经能解决——而是写扩展性问题。它把数据切成16384个槽位分散到多个主节点上每个主节点只处理一部分数据和一部分请求。读写压力分摊到多台机器才真正打破了单点写的天花板。这也是我后来在压测环境里反复对比得出的结论3主3从的Cluster在相同并发下写吞吐量可以做到单实例的2.5倍以上而且随着主节点数量增加性能还能继续线性扩展在当前机器能力范围内。另外Redis Cluster还自带故障转移机制。从节点会在主节点失联后自动选举成为新的主节点不需要额外部署哨兵进程。这意味着你少维护一套组件同时获得分片和HA架构上更简洁。当然它也有代价。最直观的代价就是涉及多键的操作会受限事务和Lua脚本的约束变多客户端必须处理重定向逻辑运维复杂度也上升不少。但当你确认写扩展是硬需求时这些代价就得认。这篇文章我会把整个底层原理、搭建步骤、扩容缩容、运维风险完整过一遍全程没有玄学都是我实际配置和踩坑过程中的真实记录。2. 数据分片、槽位计算与节点通信机制拆解2.1 虚拟槽为什么不是按ID范围而是按16384个slot切分Redis Cluster的数据分片不是简单地把key的哈希值取模按比例分配也不是像一致性哈希那样划连续区间。它定义了一个固定大小的虚拟槽空间16384个槽。每个主节点负责其中的一段或者多段slot区间比如节点A负责0-5500节点B负责5501-11000节点C负责11001-16383。当你要读写一个key的时候整个判断链路是这样的客户端先对key计算CRC16校验值对16384取模得到槽位编号slot CRC16(key) % 16384客户端根据本地缓存的集群映射表定位到该槽位所在的主节点请求发到对应主节点主节点处理并返回。看到这里你应该明白为什么Redis官方推荐使用哈希标签hash tag。如果多个key没有共同的标签它们会散落在不同的槽位多键操作在集群模式下要么报错、要么不能跨节点执行。而如果key都包含相同的{}部分比如user:{10001}:profile和user:{10001}:ordersCRC16只会对花括号里的内容计算哈希这两个key必然落到同一个槽位。我们当时把所有业务key都按用户维度加了hash tagLua脚本和事务才真正跑得起来。2.2 为什么不直接用一致性哈希而选择固定槽位不少人第一次接触这个设计时会问“为什么不用一致性哈希环不是更容易迁移数据吗”这是我早期也在纠结的问题。实际看下来固定槽位的好处非常明显数据迁移的粒度是确定的。一个slot要么在节点A要么在节点B不存在部分迁移的状态。一致性哈希则把数据按“环上的区间”归属节点区间往往跨越多个节点迁移时边界判定更复杂。集群状态可以比对。任意两个节点只需要交换各自负责的slot范围立刻能判断数据归属是否一致。一致性哈希则很难用这种方式做快速对账。手动调度灵活。你可以在某个节点上只迁移指定的几个slot这在扩容缩容时非常有用。16384这个数字也不是拍脑袋定的。每个节点的心跳包中需要携带整个slot的bitmap16384个bit刚好是2KB放在每个心跳包里网络开销可以接受。而如果取65536个槽bitmap就变成8KB心跳包明显变大。再加上主节点数量受总线连接数限制实践中几百个主节点已经是极限场景16384槽完全够用。2.3 Gossip协议集群节点间怎么知道谁活着、谁挂了Cluster节点之间通过二进制总线端口普通客户端端口 10000通信用的是Gossip协议。简单说每个节点定期向其他节点发送ping/pong消息消息里不止带自己的状态还会带一部分其他节点的信息。这样经过几轮消息传播集群里每个节点的状态最终会达成一致。我搭好集群后常用cluster info查看cluster_known_nodes有时会看到一些疑似失联的节点这属正常——Gossip本身是异步的只要几秒钟内能更新状态就没问题。维系心跳的关键参数有两个cluster-node-timeout默认15000毫秒。如果主节点在这个时间内没有收到某个节点的响应就会把对方标记为疑似下线PFAILcluster-ping-intervalGossip消息发送间隔默认是1秒但实际会根据节点下线情况自动调整。故障切换不是靠个别节点说了算而是要等持有槽位的某个主节点与目标失联超过超时时间并向集群中的其他主节点发起投票超过半数主节点同意后从节点才会提升为新主节点。这里的“半数”和Raft选举中的逻辑非常像。2.4 重定向机制失败三次我才彻底看懂的MOVED与ASK首先客户端请求一个key会先按照本地缓存的映射表把请求发给某个节点。如果槽位映射已经过时接收请求的节点会返回一个错误MOVED slot 节点IP:port意思是“你得去这个节点执行请求”。客户端需要更新本地映射缓存然后重新发送请求。ASK slot 节点IP:port意思是“这个slot正在迁移中数据可能在这个节点也可能还在原节点你可以问一下它”。很多人一开始容易把ASK和MOVED搞混。我的理解方式是MOVED是永久性重定向。说明这个slot的归属已经变了客户端以后都应该把请求发到新节点。ASK是临时性重定向。它出现在槽位迁移的过程中迁移结束后就会变成MOVED或者不再出现。客户端遇到ASK时必须带上ASKING命令才能访问目标节点这是一种“仅限本次请求允许访问迁移中的slot”的开关。如果客户端不做任何深度处理每次槽位归属变化都靠报错重试请求延迟会显著升高。好在主流语言的Redis客户端都实现了集群模式比如Redis的Cluster模块、Java的JedisCluster和Lettuce内部都维护了一份节点映射缓存遇到MOVED会更新并重试遇到ASK会按流程处理。我后来在项目里统一要求使用客户端库的集群模式禁止直连单节点拼凑连接踩过的坑才少了很多。3. 从零搭建一套最小集群3主3从的完整流程3.1 环境规划与端口设计生产环境我建议至少准备6台机器3个主节点、3个从节点从节点和主节点交叉部署不要全放同一个机柜避免物理故障导致整个分片不可用。如果你只是本地测试可以用同一台机器起6个实例但端口和目录要分开。一个值得注意的设计是端口规划。Redis Cluster的每个节点开了两个端口6379客户端连接端口16379默认637910000集群总线端口用于节点间心跳和数据迁移。我在刚开始搭建时只开放了防火墙的6379端口结果节点一直报无法通信。后来才发现16379端口也是必须放行的否则节点之间连Cluster状态都同步不了。端口规划参考表节点角色客户端端口总线端口数据目录主节点1700017000/data/redis/7000主节点2700117001/data/redis/7001主节点3700217002/data/redis/7002从节点1700317003/data/redis/7003从节点2700417004/data/redis/7004从节点3700517005/data/redis/70053.2 每个节点必须开启的配置项这是redis.conf中最关键的几项。少开一个后面就会出现莫名其妙的集群异常cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 15000 cluster-require-full-coverage no appendonly yes appendfsync everysec逐项解释一下我为什么这样配cluster-enabled yes开关必须开启。cluster-config-file集群节点信息文件文件名要按端口区分不要所有节点都用默认值。cluster-node-timeout超时阈值。我建议生产环境放在10秒到15秒太短容易误判太长影响故障转移速度。cluster-require-full-coverage no这里曾经是我踩过的一个大坑。默认值是yes意思是如果任何一个slot没有归属节点整个集群拒绝服务。当某个节点宕机或者做维护时如果它负责的slot还没被接管所有读写都会报错。设为no后只有这部分slot的请求会受影响其余slot还能正常工作。但要心里有数这是用“部分数据短暂不可用”换“集群大部分服务可用”关键业务得结合自身容忍度选择。appendonly yes开启AOF持久化。集群模式下数据分散在多台机器如果一条数据消息在时间窗口内出了故障没有持久化就可能直接丢。这里我多说一句cluster-require-full-coverage的问题。我认识一位同行集群里某个主节点硬盘故障内存里数据在重启前就有不少丢失又因为这项配置是默认yes整个集群服务全部瘫痪。业务方当时直接炸锅。如果你不能容忍“全部不可用”务必改成no并配合监控告警尽量缩短故障窗口。3.3 启动实例并完成握手每个节点单独指定配置文件启动确认进程起来后再慢慢初始化集群redis-server /data/redis/7000/redis.conf --port 7000 redis-server /data/redis/7001/redis.conf --port 7001 redis-server /data/redis/7002/redis.conf --port 7002 redis-server /data/redis/7003/redis.conf --port 7003 redis-server /data/redis/7004/redis.conf --port 7004 redis-server /data/redis/7005/redis.conf --port 7005然后让节点互相发现。传统做法是用redis-cli --cluster create一次性把节点组成集群Redis会比较贴心地帮你完成槽位分配。本地测试时直接这样执行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的意思是每个主节点分配1个从节点。命令执行后redis-cli会列出一套分配方案并询问你是否接受。确认后它会自动把16384个槽全部分配好并且为每个主节点指定一个从节点。执行完毕后可以登录任意节点验证redis-cli -p 7000 -c 127.0.0.1:7000 cluster info cluster_state:ok cluster_slots_assigned:16384 127.0.0.1:7000 cluster nodes当你看到cluster_state:ok说明集群已经进入正常工作状态。3.4 验证读写时不要忽略-c参数在这个环节新手最容易困惑的就是为什么我用redis-cli写入成功但读不到因为普通redis-cli默认不是集群模式。你连上7000端口写入一个key这个key经过CRC16计算后槽位可能不在7000节点上。单机模式它只操作当前节点的数据不感知集群重定向。想要体验集群的自动重定向必须加上-credis-cli -p 7000 -c 127.0.0.1:7000 set user:10001:name zhang - Redirected to slot [6798] located at 127.0.0.1:7002 OK你会看到客户端自动跳到7002节点执行写入。对这才说明集群路由生效了。这也是一个很容易和生产环境混淆的地方——生产代码里如果用了集群客户端处理逻辑是自动重定向的但命令行自查时忘了-c就会出现“数据明明写入成功却查不到”的假象。4. 集群稳定运行后客户端和自己的代码都要跟上4.1 客户端选型建议使用官方Cluster模式别手写路由我见过一些团队为了省依赖直接连接某个固定节点发送请求然后自己解析MOVED错误再做二次转发。表面看能用实际一上线就不行了每次槽位迁移、节点上下线、故障转移手工转发逻辑很容易出现死循环或者大量连接重连延迟也会爆炸。主流语言的官方客户端都提供Cluster模式Pythonredis-py的RedisClusterJavaJedisCluster或Lettuce的集群连接Gogo-redis/redis的ClusterClientNode.jsioredis内置集群支持这些客户端会自动维护槽位映射定期通过CLUSTER SLOTS刷新路由处理MOVED/ASK并且在节点故障时把请求切换到其他节点写业务代码时你基本感知不到底层转移。如果项目里用的是Spring集成Lettuce是比较主流的选择。我自己的习惯是连接池配置要合理不要设置“无限等待连接”否则在故障转移瞬间大量请求排队超时也不要把超时设得太短否则节点切换时会触发大量客户端报错。4.2 跨slot的批量操作你迟早要撞上的墙集群模式下MSET、MGET、PIPELINE、Lua脚本都有约束。在单机Redis里MSET可以把多个key一次写入集群里只要这些key不在同一个slot节点就会返回CROSSSLOT错误。我当时接手的一个业务登录模块用MGET一次性取用户的多个配置项所有key都用了user:{uid}:xxx这种命名所以碰巧全在同一个槽没有问题。但有些团队key命名没有统一规则遇到CROSSSLOT就只能拆分循环请求性能下降非常明显。解决思路只有这么几种给需要批量操作的key加hash tag让它们落到同一个槽在客户端根据槽位对key做分组每个组单独发MGET最后合并结果如果批量操作本身不是强一致需求可以拆成并行请求性能反而更好。这里提醒一个细节hash tag太长或太多数据倾斜风险会增大。比如所有key都写一个固定标签{global}那整批数据全挤在一个节点上分片形同虚设。哈希标签要用在有明确业务聚合的地方比如同一个用户、同一个订单、同一个店铺而不是“反正加个括号就行”。4.3 一次真实的DBA“经验”槽位迁移时垃圾连接会让应用雪崩有一次我帮某团队排查线上延迟突然飙升的问题。现象是应用服务没有发版数据库负载也没显著变化但Redis请求耗时从1ms涨到500ms以上。进场看了半天发现他们运维正在做集群扩容从一个节点把两千个slot迁移到新节点。应用侧的旧连接没有及时回收全部堆积在旧主节点上。迁移过程中旧节点既要处理业务请求、又要响应迁移命令连接数一多性能直接崩掉。根因不是Redis迁移本身有多重而是他们应用的连接池配置过大、空闲连接回收太慢导致大量连接同时和旧节点保持着TCP长连接。排查链路查看client list发现5000连接集中在同一节点查看info clientsconnected_clients很高查看CPUsys态占比高检查应用连接池配置了1000个最大连接但空闲超时设了8小时止损方案应用侧重启连接池并配合运维批量迁移slot延迟快速恢复。事后我总结的规范是连接池大小宁小勿大空闲连接回收时间控制在30-60秒涉及槽位迁移或扩缩容操作前先让运维和应用owner对齐准备好止损预案。5. 扩容缩容与故障切换实操细节全部交代清楚5.1 加一个主节点从银行借钱到真正干活的过程假设现在集群有3主3从你需要新增一个主节点7006。步骤分两步第一步把节点加进来redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000这样7006节点就加入了集群但此时它还没分到任何slot不承担数据写入。你可以通过redis-cli -p 7006 cluster slots确认它目前接管的slot列表是空的。第二步把别的节点的slot迁一部分给它redis-cli --cluster reshard 127.0.0.1:7000命令会交互式询问计划迁移多少个slot。比如你准备迁2000个填2000接着问你“接收节点的node ID是什么”可以通过cluster nodes查到再问“从哪些源节点移出”一般填all意思是集群现有主节点按各自slot比例分摊指出。这个动作本质上是把slot内的数据逐条搬到新节点。过程不是瞬间完成的可以分批次做。如果数据量很大最好在业务低峰期执行并且持续观察集群状态。5.2 给新主节点补从节点高可用别忘记这一步很多人在测试环境只加主节点不配从节点。集群确实能跑起来但你引入了一个单点一旦7006挂了它负责的slot就没人接管数据直接不可用。补从节点的命令是redis-cli --cluster add-node \ 127.0.0.1:7007 \ 127.0.0.1:7000 \ --cluster-slave \ --cluster-master-id 7006节点的node_id命令执行后7007会开始从7006全量同步数据。同步期间7006的写压力会有所上升可以在低峰期做。我通常会这样验证登录7007执行redis-cli -p 7007 -c 127.0.0.1:7007 cluster nodes看到7007的role显示为slavemaster指向7006就说明配置成功。5.3 下掉一个节点先迁槽、再删节点、最后下线进程很多人直接从进程层面把节点kill了以为集群会自动把slot分配到别处这是大错特错。集群不会替你做数据再平衡下落不明的slot会让cluster_state进入异常尤其当cluster-require-full-coverage是yes时整个集群都会不可写。正确流程把节点上的slot迁移到其他主节点redis-cli --cluster reshard --cluster-from 节点ID \ --cluster-to 接收节点ID \ --cluster-slots 待迁移槽数量 \ 127.0.0.1:7000确认被迁移节点的slot已经变为0redis-cli -p 7002 cluster slots从集群元信息中移除该节点redis-cli --cluster del-node 127.0.0.1:7000 节点ID停止进程清掉这台机器的相关配置。如果这个节点是挂在某主节点下的从节点也可以直接执行del-node下线。主节点必须先把slot迁走再下线。5.4 故障转移从节点顶上之后客户端会自动切换吗Redis Cluster的故障转移流程是自动的。假设主节点7000宕机它的从节点7003会在cluster-node-timeout之后发起选举。集群其他主节点投票同意后7003变为主节点并接管原来7000的slot。整个过程默认一般在15秒到30秒之间完成。此时代码里的客户端会经历这样的变化它感知到连接某个主节点失败内部刷新路由表听从cluster nodes的结果发现7000的角色变成了fail7003变成master把对应slot的请求指向7003。如果你的客户端库版本比较老或者设置过于保守故障转移期间会有大量报错。我个人建议客户端层面的连接超时不要设太大操作超时可以适当放大同时要打开字符串异常日志别把这些错误当垃圾日志静默吞掉。5.5 手动切换主从怎么优雅地把写能力挪回原主节点故障恢复后原主节点7000重新启动它会作为从节点挂到7003下面。但有些业务希望主节点回到原位置可以执行redis-cli -p 7000 -c 127.0.0.1:7000 CLUSTER FAILOVER这条命令会触发一次平滑的主从切换新的从节点同步完增量数据后主从角色互换。和直接kill重启不同CLUSTER FAILOVER会尽量保证数据一致。没有什么特殊情况我建议都用这个命令别用CLUSTER FAILOVER FORCE。6. 集群运维中的那些隐藏风险都是真金白银换来的教训6.1 慢查询和热点key在集群里会被放大集群分片之后单个节点的CPU负载看似均衡但是热点key造成的资源倾斜依然存在。比如某个爆款商品的详情页所有流量都集中在一个key上这个key所在的节点CPU跑满其他节点却很空闲。缓解手段包括对读多写少的热key做本地缓存降低Redis访问量对一些统计类key做“多副本写入”比如拆成hotkey:0到hotkey:9读取时随机取一个但这会带来一致性问题不适合强一致场景在业务侧进行削峰避免瞬时请求打爆。慢查询同样值得监控。slowlog get是排查利器如果你发现某个节点慢查询持续变多最好是先把客户端流量切一部分走再定位是key太大、命令太耗CPU还是持久化配置导致的阻塞。6.2 带宽承载不能只看机器网卡还要算上集群内部流量很多人估算带宽时只算了业务入口带宽没算节点间总线的流量。槽位迁移时数据要从源节点拷到目标节点故障转移时从节点要全量同步主节点的数据日常Gossip消息虽然小但节点多了以后也会累计。如果机器带宽本来只有1Gbps在大规模迁移时很容易把网卡打满直接影响业务请求的延迟。我的建议是机器预留带宽要在峰值业务的2倍以上迁移作业尽量限速Redis提供了redis-cli --cluster reshard --cluster-slots 1000 --cluster-yes这样的分批方式一批批地做不要一口气全迁完。6.3 持久化配置不一致会导致“幸存”节点丢数据集群模式最容易被忽略的一个点是不同节点的持久化策略不一致。正常生产配置应当是全部开启AOF且appendfsync统一。但有些团队扩容时直接复制了老节点的配置文件改一下端口就上线老节点上如果save策略不同新节点就会沿用不对的配置。一旦主节点宕机从节点基于不完整的持久化数据做主从切换集群会回复旧数据甚至出现恢复后的数据比切换前要旧的情况。我在实际中遇到过一次明明是主从切换切换后部分用户信息却看不到。查到最后是主库的RDB快照频率占比太长从库AOF又只配置了everysec在异常断电窗口里丢了近2秒的写操作。对弱一致业务这还能容忍但对订单、余额、状态类数据这种丢数据的后果很严重。6.4 脑裂网络分区时两个“主”各自写入怎么办虽然Redis 7引入了更严格的raft选举机制但只要有网络分区就可能出现完全断开的两部分都认为自己是正常集群的情况。在网络分区恢复之前如果旧主节点仍在接收写请求而新主节点已经当选就会产生脑裂窗口一部分请求写到了旧主节点而旧主在分区恢复后被降级为从节点会执行全量/增量同步覆盖掉新主节点上的部分数据。Redis本身没有像数据库那样复杂的防脑裂仲裁机制唯一能做的是通过配置尽量缩短认错时间。cluster-node-timeout调小意味着更快触发故障转移但也会增加对临时抖动误判的风险调大则反过来。这是一个两边权衡的参数没有标准解。对于金融级别的一致性需求Redis Cluster通常不是无脑选择MySQL、分布式存储方案才是。6.5 平时不怎么关注但关键时刻要命的参数清单参数作用建议cluster-node-timeout节点故障判定阈值10-20秒cluster-replica-validity-factor控制从节点与主节点失联多久后拒绝提升默认10cluster-migration-barrier控制从节点迁移的最小从节点数默认1cluster-require-full-coverage槽位未覆盖时是否拒绝服务根据业务容忍度谨慎设置appendonlyAOF持久化开关生产环境开启appendfsyncAOF落盘策略从一致性要求看可考虑alwaysmaxmemory-policy内存淘汰策略统一配置避免各节点不一致7. 我踩过的坑最终沉淀成的检查清单最后分享一个我每次搭集群或做迁移都会过一遍的检查清单按照这个走基本能避开大部分低级问题。[ ] 所有节点是否都开启了cluster-enabled yes端口规划是否区分了客户端端口与总线端口[ ] 防火墙是否放行了总线端口节点间Gossip包是否能互通[ ] 主从节点是否开启了AOF持久化且配置策略一致[ ] 应用客户端是否使用的是集群模式且连接池大小合理[ ] 批量操作涉及的key是否使用了统一的hash tag并检查是否有冲突[ ] 迁移前是否打印了槽位分布cluster slots迁移过程中是否分批次执行[ ] 迁移后是否验证了cluster_state:ok负责迁移的新节点是否确实能读到旧数据[ ] 下掉节点前slot是否已经全数迁走节点ID是否已从集群中移除[ ] 故障转移后客户端路由表是否自动更新是否需要手动刷缓存[ ] 是否有慢查询监控、节点CPU监控、内存告警防止热点倾斜演变成故障。我也再分享一个关于“测试环境一定要做故障演练”的建议。只把集群搭起来看状态是不够的最好每隔一段时间做一次真实演练随机kill一个主节点观察从节点是否如期顶上、客户端是否自动重连、数据是否有丢失然后恢复原主节点手动切回去。这一步能帮你提前发现很多配置问题远比等生产环境出问题再补救来得划算。Redis Cluster是一个成熟的分片方案但它不会替你解决所有架构问题。理解了槽位到底怎么分、数据怎么迁、故障怎么切你才能真正驾驭它。如果这篇文章能帮你少踩几个坑、少熬几次夜那这波分享就值了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑