主从切换后客户端应主动校验节点角色而非依赖超时或错误重试:每次写前执行ROLE命令,返回slave则丢弃连接;连接池需设短空闲时间(2–5分钟)并禁用PING检测,改用语义化验证。

主从切换后客户端还在用旧 redis-cli 连接?立刻断开别等超时
Redis 主从切换(比如哨兵触发 failover 或 Cluster 重分片)后,旧主节点降为从,但客户端可能还连着它发写命令——这时会收到 READONLY You can't write against a read only replica 错误。这不是“缓存穿透”,而是连接错位。真正要防的是:错误连接没及时释放,导致后续请求持续打到只读节点,堆积失败、拖慢业务。
关键不是等错误返回再重试,而是主动感知拓扑变更、快速熔断旧连接。客户端 SDK 通常不自动处理这个,得自己加一层判断逻辑:
- 每次执行写命令前,先发
ROLE命令确认当前连接节点角色;如果返回["slave", ...]就直接丢弃该连接,不发业务命令 - 用连接池时,别复用“疑似过期”的连接——给连接加
lastRoleCheckAt时间戳,超过 30 秒没检查就强制重检 - 避免在
try/catch里捕获READONLY后才重建连接:这已经晚了,至少一个请求失败,且可能触发下游重试放大流量
预热不是往新主塞数据,是让客户端提前拿到新主地址
所谓“预热”,不是指把热 key 提前 SET 到新主上——新主本来就有全量数据(同步完成的前提下)。真正卡点在于客户端 DNS 缓存、连接池缓存、或硬编码的 IP 地址还没更新,导致请求继续发向旧地址。
必须让客户端在切换发生前/发生时,就拿到新主的真实连接信息:
- 用 Redis Sentinel?监听
+switch-master事件,收到后立刻调用客户端 SDK 的refreshSentinels()或类似方法(如 Jedis 的sentinelRefresh()) - 用 Redis Cluster?确保客户端启用了
cluster-require-full-coverage no,并定期调用CLUSTER SLOTS更新槽映射,不要依赖启动时的一次性加载 - 自建代理层(如 Twemproxy、Codis)?切主后必须触发代理配置热更新,并让客户端走代理而非直连 —— 直连永远追不上切换速度
READONLY 错误本身不能当熔断信号,得结合连接生命周期判断
单看一次 READONLY 报错,可能是临时网络抖动、命令发错节点、甚至只是某个从节点被误标为主。直接据此熔断整个连接池,容易引发雪崩。
更稳妥的做法是分层响应:
- 单次命令报
READONLY→ 记录日志 + 上报监控,但不熔断,仅标记该连接为“待验证” - 同一连接连续 2 次写命令都返回
READONLY→ 立即关闭该连接,从池中移除 - 连接池中超过 30% 连接被标记为“待验证” → 触发全量拓扑刷新(如重拉
SENTINEL GET-MASTER-ADDR-BY-NAME),并清空旧连接
注意:Jedis 默认不重试 READONLY,Lettuce 默认会重试,但重试目标仍是原连接——所以重试机制反而掩盖问题,建议关掉写命令的自动重试。
别信“连接空闲检测”,它救不了主从切换场景
很多客户端配置了 testOnBorrow 或 validateConnectionOnIdle,以为能提前发现连接失效。但在主从切换中,连接本身是通的,TCP 没断,PING 也成功,只是角色变了。空闲检测只会浪费资源,根本判不出问题。
必须用语义化检测代替连通性检测:
- 禁用所有基于
PING的空闲验证,改用ROLE或INFO replication - 如果 SDK 不支持自定义验证命令(如老版本 Lettuce),就在获取连接后、执行业务命令前,强制插入一次
ROLE调用 - 对高敏感服务,甚至可以把
ROLE结果缓存在本地,配合哨兵事件做增量更新,避免每次请求都多一次 RTT
最易被忽略的点:连接池的“最大空闲时间”设得太长(比如 30 分钟),会导致大量连接长期滞留旧节点,哪怕拓扑已变——这个值建议压到 2–5 分钟,配合主动角色校验,才真正可控。

















