1. 为什么分片集群需要自动故障转移
我之前接手一个电商后端项目时,缓存量上来之后最先扛不住的不是 CPU,而是单机 Redis 的内存。数据量一涨,maxmemory一设,淘汰策略一触发,缓存命中率唰唰往下掉,业务方天天找上门。后来把架构从“单机 + 主从哨兵”切到 Redis 分片集群(Cluster),这一刀切完容量是够了,可真正上线前做故障演练时才发现,“自动故障转移”这六个字背后藏着一条完整的判定链路,从节点标记、投票、晋升、槽位接管到客户端感知,任何一个环节配错,故障转移要么不触发,要么触发了业务照样超时。
这篇不是复制官方文档,是我把一套 6 节点的 Redis Cluster 从部署、模拟宕机到观察故障转移全流程跑完之后的经验总结。适合谁看?适合正在从哨兵方案迁移到 Cluster、准备做高可用压测、或者被“redis command timed out”这类偶发超时折腾的运维和 Java/Go 后端同学。看完之后你能回答这几个问题:从节点什么时候有资格晋升、为什么有些集群宕了一台主节点半天切不过来、故障转移期间客户端到底会不会断连、以及哪些参数是必须调的。
1.1 从“主从哨兵”到“分片集群”是一次架构必然
先别急着上手敲命令,得先搞清楚为什么一定要有自动故障转移。Redis 主从复制解决的是数据冗余和读扩展,哨兵(Sentinel)解决的是主节点宕机后的自动切换。这套组合拳在小数据量场景下非常好用,主节点写、从节点读,主节点挂了哨兵把从节点提升上来,业务无感知。
但它的天花板也很明显:所有写流量还是压在一台主节点上,单机内存上限决定了整个缓存集群的容量上限。网上很多教程拿“黑马点评”这类电商实战项目练习时,最开始用的也是单机或主从模式,等到你要缓存商品详情、库存、用户会话、热点榜单时,一台 8GB 内存的机器根本不够放。此时你面临的选择是:纵向扩容(换更大内存的机器,贵且到头)或横向分片。
横向分片的本质就是把 key 按照某种规则散到多台机器上,每台只存一部分数据。Redis Cluster 用的是哈希槽(hash slot)方案,一共 16384 个槽,通过CRC16(key) % 16384算出 key 落在哪个槽,再由槽映射到具体节点。这样加机器时把一部分槽挪过去就能水平扩展,数据不用全量重新分布。
但“分片”只是解决了容量问题,没解决可用性问题。任意一台分片节点宕机,它负责的那部分槽就无法读写,整个集群等于残废。所以 Redis Cluster 的每个分片内部仍然保留了“一主一从”甚至“一主多从”的结构,分片内主节点负责读写,从节点只是复制数据;一旦主节点故障,从节点要能自动顶上,否则分片集群依然是脆弱的。
1.2 “分片”和“自动故障转移”为什么必须一起谈
很多初次用 Cluster 的同学会误解一件事:以为只要把多个 Redis 实例用redis-cli --cluster create串起来,集群就天然高可用了。实测不是这样。Cluster 的自动故障转移是一个独立运行的机制,需要同时满足节点心跳超时判定、客观下线确认、从节点竞选、槽位接管四个阶段才算完整。
我见过一个真实案例:某团队部署了三主三从的 Cluster,压测时拔掉一台主节点网线,等了两分钟业务还在报错,查cluster nodes发现从节点一直处于disconnected状态,根本没有发起竞选。后来排查发现是cluster-node-timeout设置成了 60 秒,主观下线判定拖太久,且部分节点的cluster-replica-validity-factor默认值加复制延迟导致从节点主动放弃了竞选资格。
所以说,自动故障转移不是某个开关一打开就完事,它背后是一整套分布式共识流程。下一节我把这条链路从头到尾拆开讲,你看完再去配置,才知道每个参数为什么要这么设,而不是照着别人的redis.conf抄。
2. 拆解故障转移闭环:从心跳超时到新主接管
自动故障转移全流程可以拆成四步:主观下线(PFAIL)、客观下线(FAIL)、从节点竞选(failover election)、角色切换与槽位接管。很多人只知道“主节点挂了会自动切换”,但没搞懂每一步的触发条件和投票规则,排查问题时就会非常被动。
2.1 主观下线 PFAIL 与客观下线 FAIL 的判定差异
Redis Cluster 中的每个节点都通过 Gossip 协议定时向其他节点发送 ping/pong 消息,消息里带上自己视角的部分节点状态。正常网络下,节点之间每秒有一次心跳。如果节点 A 在cluster-node-timeout时间内没有收到节点 B 的有效响应,A 会在本地把 B 标记为PFAIL(possible failure,主观下线)。
主观下线的“主观”二字很关键:它只是 A 单方面的判断,可能是网络抖动、GC 停顿、CPU 飙高甚至负载均衡层丢包导致的误判,所以 PFAIL 状态并不会直接触发故障转移。要让 Cluster 真正认为某个主节点挂了,需要走客观下线流程:节点 A 会通过 Gossip 消息把“我认为 B 是 PFAIL”的信息传播出去,当集群中持有槽位的主节点里,大多数都认为 B 不可达时,B 才会被标记为FAIL(客观下线)。
这里要注意“大多数”不是全集群节点,而是持有槽位的主节点。比如三主三从集群,三个主节点中至少两个都认为 B 不可达,B 才能从 PFAIL 变成 FAIL。这样设计的目的是避免一个节点误判就导致整个分片切换。默认的客观下线流程要求多数持有槽位的主节点确认,这意味着如果网络分区比较恶劣,只有一部分主节点能收到 PFAIL 消息,客观下线就会悬而不决,从节点一直等不到晋升信号。
2.2 从节点竞选的资格与投票规则
主节点被标记为 FAIL 之后,持有槽位的主节点会向集群广播一条 FAIL 消息,此时处于该分片内的从节点开始判断自己是否有资格竞选。判定的核心指标是复制偏移量(replication offset)和cluster-replica-validity-factor。
复制偏移量代表从节点已经同步到主节点的数据位置。如果主节点宕机前刚写入了一条数据,从节点还没来得及复制,此时从节点晋升就会丢这条写请求。cluster-replica-validity-factor默认是 10,它乘以cluster-node-timeout得到允许的最大复制延迟;如果从节点的复制偏移量落后主节点太多,超过这个阈值,它会放弃竞选,因为晋升上去也是数据残缺,不如等原主恢复或人工介入。
符合资格的从节点不会立刻发起竞选,而是等待一个随机延迟,延迟范围是0 到 cluster-node-timeout * (replica-priority + 1)中的一个随机值,其中replica-priority越小代表优先级越高。这样设计是防止多个从节点同时投票,导致票数分散、谁都无法过半;也让数据最新、优先级最高的从节点大概率先发起请求并拿到多数票。选举采用 Raft 风格的规则:从节点发送FAILOVER_AUTH_REQUEST,只有持有槽位且状态正常的主节点有权回复FAILOVER_AUTH_ACK,从节点拿到超过半数主节点的 ACK 后,就赢得本次故障转移的胜利。
2.3 槽位接管的真实机制与 epoch 纪元
我最初有个误解,以为主节点下线后,从节点晋升还要把槽里的数据从其他节点搬过来,后来看代码和实测才发现根本不是一回事。从节点在平时一直在复制主节点的全量数据,两者数据几乎一致,所以从节点晋升后是直接“继承”原主节点持有的所有槽位,不需要跨节点迁移数据。
实现方式是通过 epoch(纪元)机制。每轮故障转移都会产生一个新的currentEpoch,赢得竞选的从节点把自己的 epoch 更新为更大的值,并向全集群广播 PONG 消息,声明“我现在是这些槽的新主节点”。其他主节点收到这个消息后更新自己的槽位映射表,后续客户端无论连到哪个节点,都会通过 MOVED 重定向到新主节点。
如果原主节点之后恢复上线,它会发现当前集群纪元比自己记录的 epoch 更大,同时自己的槽位已经被别人接管,于是自动降级,尝试临时复制新主节点来继续同步数据。这个设计保证了故障转移后,只要复制链路没断裂,数据不会出现大规模丢失,但也带来了一个经典的分布式系统问题——网络分区下旧主节点仍在运行导致的写冲突窗口,这个在第四章细聊。
3. 实操:6 节点分片集群部署与故障转移演练
理论讲了半天,现在开始动手。我用的是 Redis 7.0 版本,在单台 Linux 服务器上用 Docker 起 6 个节点(3 主 3 从),然后杀掉其中一个主节点,完整观察自动故障转移的全过程。这套流程你在自己机器上也能复现,30 分钟跑完。
3.1 准备 6 个 Redis 节点的 docker 配置
用 Docker 是最快的,但有一个要注意的点:Redis Cluster 的节点之间通过 Gossip 端口(默认是当前端口 + 10000,即 7000 对应 17000)通信,容器部署时必须把两个端口都映射出来,否则节点之间互相发现不了。我习惯用一个目录统一放配置文件,每个实例一个子目录。
先创建配置文件模板,以 7000 端口为例,/opt/redis-cluster/7000/redis.conf内容如下:
port 7000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 5000 appendonly yes appendfilename "appendonly-7000.aof" protected-mode no daemonize no解释几个关键参数:cluster-enabled yes是开启集群模式;cluster-config-file是集群拓扑信息的持久化文件,Redis 会把槽位映射、节点 ID 等信息写进这个文件,下次启动时直接恢复;cluster-node-timeout 5000表示节点心跳超过 5 秒没收到响应就标记 PFAIL,这是我建议的默认值,太大会拖慢故障转移,太小容易误判。剩下 7001 到 7005 五个目录里的配置文件只改端口号和文件名。
启动容器时我在一个自定义网络里依次启动:
docker network create cluster-net for port in 7000 7001 7002 7003 7004 7005; do docker run -d --name redis-$port \ --network cluster-net \ -p $port:7000 \ -p $((port + 10000)):17000 \ -v /opt/redis-cluster/$port/redis.conf:/usr/local/etc/redis/redis.conf \ redis:7.0 redis-server /usr/local/etc/redis/redis.conf done容器内固定使用 7000 和 17000 端口,宿主机把 7000~7005 映射到容器的 7000,把 17000~17005 映射到容器的 17000。容器之间通过cluster-net自定义网络直接用内网 IP 通信,不依赖宿主机端口转发;宿主机映射端口只是为了方便本机用redis-cli -p 7000连接调试。
提示:不要试图把 Gossip 端口改成 10001、10002 这类自定义值,除非你同时改
cluster-port配置并在所有节点上保持一致。节点之间互通时会从配置里读取端口信息,一旦各节点端口不统一,拓扑会发现“我连了你的 7000,但你告诉我你的端口是 17001”,非常容易错乱。
3.2 创建集群并确认主从与槽位分布
六个节点都起来后,用官方的集群创建命令一把梭:
redis-cli --cluster create \ 172.20.0.2:7000 172.20.0.3:7001 172.20.0.4:7002 \ 172.20.0.5:7003 172.20.0.6:7004 172.20.0.7:7005 \ --cluster-replicas 1这里我直接用了容器在cluster-net网络里的 IP,因为节点之间的 Gossip 要把 IP 写进自己的拓扑文件,如果用127.0.0.1创建,跨容器通信会全部失败。如果你的环境不方便手动查 IP,可以用docker inspect redis-7000 | grep IPAddress获取。
命令里的--cluster-replicas 1表示每个主节点配 1 个从节点。Redis 会自动分配主从角色,尽量把主节点和它的从节点分到不同容器。创建成功后看一下节点状态:
redis-cli -p 7000 cluster nodes输出类似:
<node_id_7000> 172.20.0.2:7000@17000 myself,master - 0 0 1 connected 0-5460 <node_id_7001> 172.20.0.3:7001@17001 master - 0 0 2 connected 5461-10922 <node_id_7002> 172.20.0.4:7002@17002 master - 0 0 3 connected 10923-16383 <node_id_7003> 172.20.0.5:7003@17003 slave <node_id_7000> ... <node_id_7004> 172.20.0.6:7004@17004 slave <node_id_7001> ... <node_id_7005> 172.20.0.7:7005@17005 slave <node_id_7002> ...确认三个主节点分别持有 0-5460、5461-10922、10923-16383 三组槽位,三个从节点各挂一个主节点。此时cluster info里的cluster_state:ok、cluster_slots_assigned:16384才是健康状态。
3.3 模拟主节点宕机,观察自动切换全过程
演练开始前,我先把一批测试 key 写进 7000 节点负责的槽位区域,方便之后验证数据是否可读:
for i in {1..1000}; do redis-cli -c -p 7000 set key:$i value:$i > /dev/null done-c参数表示集群模式,客户端会自动处理 MOVED 重定向。确认 key 都写进去后,直接杀掉 7000 节点的容器:
docker kill redis-7000注意这里我用kill而不是pause,因为进程直接消失会让节点心跳立刻失联。如果是模拟网络抖动,用pause更合适。接下来观察 7003 从节点的状态变化。因为cluster-node-timeout是 5 秒,所以大约 5 秒后其他节点会开始把 7000 标记为 PFAIL,再经过一轮 Gossip 传播和投票,真正完成客观下线还需要 1 到 2 秒。
实测从 kill 到 7003 成功晋升为主节点,大约用了 6 到 8 秒。期间不停执行:
redis-cli -c -p 7001 cluster nodes | grep 7003约 8 秒后输出变成:
<node_id_7003> 172.20.0.5:7003@17003 myself,master - 0 0 4 connected 0-5460这意味着 7003 已经从slave变成了master,并且完整接过了原来的0-5460槽位。再执行redis-cli -p 7003 get key:500,能正常返回数据。整个故障转移过程中,写入命令会短暂报错CLUSTERDOWN(在客观下线达成前)或者MOVED重定向,客户端如果配置了自动重连和拓扑刷新,业务侧只会感受到一次短暂的命令超时。
3.4 原主恢复后的角色回退机制
演练还没有结束。我把 7000 容器重新启动,等它重新加入集群后查看cluster nodes,会发现原来的 7000 主节点已经变成了slave,并且挂在了新的 7003 主节点下面。Redis 用 epoch 机制识别出自己已经“过气”,自动放下身段开始复制新主节点的数据。
这里有个实用技巧:如果原主节点恢复后不想让它作为从节点挂在老分片里,想让它重新拿回主角色,可以先redis-cli -p 7000 cluster failover手动让 7000 从 7003 手里接管槽位;演练时就按“原主降级为从,新主继续服务”来观察即可。这个设计保证故障转移后集群不会出现两个节点同时声称持有同一批槽位的情况。
4. 自动故障转移常见坑与排查实录
部署和演练都跑完了,接下来聊点我在多个项目里真刀真枪遇过的故障转移问题。这些问题在官方文档里都有解释,但文档只告诉你“是什么”,不会告诉你排起来有多头大。
4.1 从节点不竞选,先查这三项配置
有人给我看过他集群的cluster nodes输出,主节点已经显示FAIL,但对应的从节点一直停在slave状态,过了十几秒才手动切换成功。这种情况十有八九是配置问题,我建议按这个顺序排查。
首先看cluster-node-timeout是否设得过大。有人图省事直接抄网上配置把超时设成 30000 甚至 60000,主观下线就得等 30 秒,客观下线还要等一轮 Gossip 周期,故障转移总耗时自然被拉长。这个值我建议生产环境 5000 到 10000 之间,太小会把 GC 停顿当故障,太大则切换太慢。
其次看cluster-replica-validity-factor。如果从节点长时间和主节点断连、复制偏移量落后太多,它会主动放弃竞选。你可以在从节点上执行:
redis-cli -p 7003 info replication观察master_link_status和master_repl_offset,如果两个值不正常,说明复制链路本身有问题,故障转移当然不会触发。
最后还有个隐蔽问题:故障发生在网络分区场景时,比如主节点 A 所在子网只有少数持有槽位的主节点,那么 A 收到的 FAIL 报告不足以构成多数,客观下线就会卡住,从节点自然不会发起竞选。这种场景不是配置错了,而是部署拓扑本身没有考虑机房冗余,排查时要站在分区角度重新审视节点分布。
4.2 转移窗口期的客户端超时与连接池适配
Java 项目里最常见的报错就是redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException。故障转移窗口期(主观下线判定 + 投票晋升)一般有几秒到十几秒,这窗口内原主节点已不可用,新主节点还没接管,客户端的命令就会堵塞到超时。很多帖子把这个报错归结为“网络慢”或者“连接池不够”,其实是没意识到自己连接的是 Cluster,而 Cluster 在做高可用切换。
客户端侧能做的有三件事:一是把命令超时时间设得比cluster-node-timeout略大,比如 5 秒的心跳超时就配 3 到 5 秒的命令超时,给故障转移留出缓冲区;二是配置连接池时不要只连一个节点,尽量传入多个种子节点地址,Lettuce/Jedis Cluster 会自动发现拓扑;三是开启自动刷新拓扑,Lettuce 里对应TOPOLOGY_REFRESH配置,默认是 60 秒刷新一次,故障转移后如果还是旧路由,客户端会一直收到MOVED重定向,刷新生效后才会稳定。
注意:Spring Boot 项目里如果只配了
spring.redis.host和spring.redis.port,客户端连的是普通 Redis 而不是 Cluster。要连集群请用spring.redis.cluster.nodes配置多个节点地址,否则你看到的“连接不上 Redis”其实是没有走到集群模式。
另外我遇到过一个蛋疼的情况:Spring Boot + Lettuce 在故障转移结束后一切正常,但新的主节点刚晋升,同一批热点 key 被并发打到其他从节点读取,从节点默认只读且可能短暂返回旧数据。如果业务允许一定延迟,可以接受;如果不行,尽量让客户端只走主节点读,或者给这些从节点挂上replica-read-only no并承担数据不一致的代价。
4.3 网络分区、锁丢失与容量覆盖策略
上面说 Redis 故障转移是“大体一致”而非强一致。具体表现为:主节点和从节点之间复制延迟超过阈值时,主节点可能已经接受了写请求,但从节点还没同步过来,此时发生故障转移,这条写请求就丢了。换到分布式锁场景就是最经典的“锁丢失”问题:线程 A 在主节点上拿到锁并写入,主节点未同步就宕机,从节点晋升后锁不存在,线程 B 也能拿到锁,两个线程同时进入临界区。
这个问题没有银弹,业界比较常见的做法是 RedLock(多节点独立锁)或者在业务层引入 fencing token(每次加锁分配递增编号,旧编号的写请求直接拒绝)。如果你不想引入额外复杂度,至少要做到两点:一是把cluster-require-full-coverage设为no,防止某个分片故障时整个集群拒绝服务;二是监控复制延迟,延迟过高时提前告警,把故障影响扼杀在引发问题之前。
5. 关键配置参数与客户端适配速查
这一节我整理一张配置速查表,把上面分散提到的参数汇总一下,方便你部署前直接对照检查。
5.1 服务端参数速查表
| 参数 | 默认值 | 我的建议 | 说明 |
|---|---|---|---|
cluster-enabled | no | yes | 必开,集群模式开关 |
cluster-config-file | nodes.conf | 每个实例独立 | 集群拓扑持久化文件 |
cluster-node-timeout | 15000 | 5000~10000 | 主客观下线判定基准,太小误判、太大切换慢 |
cluster-replica-validity-factor | 10 | 10 | 从节点允许的最大复制延迟倍数 |
cluster-migration-barrier | 1 | 1~2 | 一个主节点至少保留多少个从节点,防止大量迁移 |
cluster-require-full-coverage | yes | no | 槽位未全覆盖时是否拒绝服务,生产强烈建议 no |
cluster-allow-reads-when-down | no | 视业务 | 集群部分故障时是否允许读 |
replica-priority | 100 | 按节点能力设置 | 值越小从节点竞选优先级越高 |
特别提醒cluster-require-full-coverage。Redis 以前默认是yes,意味着只要有一个槽位不可用,整个集群的所有读写都会被拒绝。这个设计保证了强一致性,但可用性就差了。我们做过一次演练:三主三从中坏了一个主节点,故障转移尚未完成的那几秒,CLUSTERDOWN错误铺天盖地,连带其他分片的正常业务也全部被拒。改成no后,故障分片内的请求依然会报错,但其他分片能继续服务,整体影响面小很多。
5.2 客户端连接 Cluster 的配置要点
服务端配置好了,客户端也要跟着适配,否则故障转移做了也白做。Java 生态里我用 Lettuce 和 Jedis 比较多,先说 Lettuce。连接 Cluster 至少需要两个配置点:
spring: data: redis: cluster: nodes: - 10.0.0.1:7000 - 10.0.0.2:7001 - 10.0.0.3:7002 timeout: 3000ms lettuce: cluster: refresh: adaptive: true period: 10stimeout建议 3 到 5 秒,不要设成 1 秒,否则故障转移窗口期全是超时异常。adaptive: true是让客户端在收到 MOVED/ASK 时主动刷新拓扑,而不是傻等固定周期。Jedis 对应的是JedisCluster,构造时传入节点集合即可,连接池和超时参数同理。Python 的redis-py要用RedisCluster而不是普通Redis;Go 的go-redis用redis.NewClusterClient。这些客户端类库都实现了槽位缓存和自动重定向,但前提是你传的种子节点地址要正确,不能只传一个单机地址。
还有一点容易被忽略:多个客户端之间如果使用不同的序列化器,同一个业务 key 在不同端看起来可能完全不一样,这不仅影响缓存命中率,还会让你在做故障转移演练时误判“数据丢了”。统一用 String 序列化 Key,Value 按业务选择 JSON 或二进制,能少踩很多坑。
5.3 中间件场景与数据类型的选择建议
顺带聊一个我在团队里经常被问的问题:Redis 在项目里除了做缓存,还经常被当成中间件用,比如分布式锁、延迟队列、限流器、幂等表。分片集群的自动故障转移对缓存场景是透明的利好(缓存键丢了可以从数据库重建),但对中间件场景就要谨慎得多。
以分布式锁为例,如果你用的是SET key value NX PX 10000这种常规锁,一旦发生故障转移,锁可能不可靠,这点上面已经分析过;如果你的延迟队列依赖 List 的BLPOP,故障转移期间消费者会短暂阻塞,需要用超时重试来兜底。不同数据类型在故障转移下的表现也不一样:String 类型的缓存丢了大不了回源,但 Hash/ZSet 里的会话状态、排行榜如果丢了,恢复起来就要碰运气。
所以我的建议是:缓存数据可以大胆放 Cluster,但存放“业务强一致状态”的 Redis 尽量单独部署,别和缓存混用。实践里很多团队会把分布式锁单独放在一个小集群或者哨兵架构里,容量不用大,但稳定性优先,这样即便主缓存集群发生故障转移,核心锁服务也不受影响。
6. 故障转移演练的落地经验与监控
文章到这里,技术链路和参数基本讲完了。最后分享我在实际项目里总结出来的经验,以及一些可以量化故障转移效果的方法。
6.1 演练环境越接近生产,结果越可靠
第一次演练故障转移,一定要在真实网络环境里做,不要用localhost。我最初在本地 Mac 上用127.0.0.1起 6 个节点,故障转移 2 秒就完成了,后来上到云服务器,才发现跨机房网络延迟、防火墙端口、容器网络模式都会影响 Gossip 判定,实际切换耗时比本地慢了好几倍。用 Docker 演练时,建议至少跨两台宿主机,模拟真实的网络分区,而不是在一台机器上自嗨。
另外演练要分成几个等级:先手动 kill 进程,再模拟拔网线,再模拟整个机房的网络分区,最后再叠加高写入压力。每个等级暴露出来的问题都不一样。我最深的一次教训是:单纯 kill 进程时故障转移很顺,但压着每秒 5 万次写入时再 kill,从节点因为复制偏移量落后太多直接放弃了竞选,等了 30 秒业务才恢复。所以演练一定要带流量,不然只是走过场。
6.2 参数调整要凭监控数据,不是拍脑袋
故障转移不是越频繁越好,也不是越快越好。cluster-node-timeout设成 1 秒虽然能让主节点 1 秒后就被判定故障,但节点 GC 停顿 1 秒在 Java 应用里很常见,可能导致主节点明明活着却被摘掉。我见过有人把超时调到 1000ms,结果每天凌晨 Full GC 时集群自动切换十几次,数据还没丢,先把运维吓坏了。
合理的做法是先收集一段时间的监控数据,看看节点的心跳延迟 P99、GC 暂停时长、网络抖动频率,再反过来定cluster-node-timeout。如果网络本身很稳定,5 秒足够;如果跨机房部署,可能 10 秒更稳妥。replica-priority也一样,最好给从节点配置不同的优先级,比如 SSD 机器上的从节点优先级调低(数值小),让它更容易在故障时被选中,而不是两个从节点凭运气竞争。
6.3 客户端超时与降级兜底是最后防线
无论 Cluster 多高可用,客户端和调用方永远要设置超时时间,并且要有降级兜底。网上很多项目教程里,同学们连不上 Redis 第一反应是换密码、清密码、重启 Redis,但如果连接的是 Cluster,更多时候是拓扑刷新慢导致的临时不可用。给调用方设置 3 秒超时,超时后走本地缓存降级,用户体验会好很多。
我见过一个很典型的案例:三主三从集群里一台主节点所在机器网络异常,故障转移在 7 秒内完成,但因为命令超时设成了 1 秒,那 7 秒内的所有请求全部报错,监控面板上一片红。后来把超时改成 3 秒并加了本地降级,同样是 7 秒的故障转移窗口,业务反馈几乎无感。
6.4 用监控量化故障转移的效果
最后说说怎么验证故障转移真的达标了。我一般关注四个指标:cluster_state是否一直为 ok(除了切换瞬间)、cluster_slots_assigned是否等于 16384、从节点的master_link_status是否从 up 短暂变 down 后恢复、以及复制偏移量差值是否在阈值内。这些指标用redis_exporter配合 Prometheus 就能采到,没有的话写个定时脚本轮询cluster nodes也行。
演练完之后要记录一个关键数字:从故障发生到新主节点开始接收写入的总耗时。把这个数字和你的客户端超时时间比一比,只要超时时间大于故障转移耗时,业务侧大概率只会闪断一次;如果超时时间小于故障转移耗时,就要考虑调大超时或者降低切换耗时。我在几个项目里都把这个数字写进了发布手册,每次做架构变动后都要重新压测一轮,确认自动故障转移的表现没有退化。
我记得之前有一次生产故障,三主三从集群里一台主节点所在机器网络异常,故障转移在 7 秒内完成,但因为我们把命令超时设成了 1 秒,导致那 7 秒内的所有请求全部报错,监控面板上一片红。后来把超时改成 3 秒并加了本地降级,同样是 7 秒的故障转移窗口,业务反馈几乎无感。这也是我推荐你在自己的项目里先做一轮故障注入测试的原因——把最坏情况想清楚,比把参数调得完美更实际。