redis哨兵切换时的连接抖动本质是客户端未及时更新拓扑而继续向旧主发命令;jedissentinelpool依赖轮询与switch-master事件触发重发现,未收到事件则不重建连接池;lettuce需避免缓存已关闭连接,uri须用redis-sentinel://格式;resque需显式配置重连参数并搭配支持哨兵的redis-rb;dns缓存和连接池残留是常见隐形问题,需调优jvm dns缓存策略并手动清理冗余连接池。

Redis哨兵切换时的连接抖动,本质不是“连不上”,而是客户端还在往旧主地址发命令——Connection refused 或 READONLY You can't write against a read only replica 这类错误,90% 都是拓扑没及时更新导致的。
为什么 JedisSentinelPool 有时不自动切换主节点?
JedisSentinelPool 不是“监听+实时推送”,它靠轮询哨兵 + 订阅 +switch-master 事件触发重发现。如果没收到事件,就不会重建连接池。
- 检查日志里是否有
Discovered new master—— 没有就说明哨兵通信异常,或应用没成功订阅__sentinel__:hello频道 - 确认初始化时传入的是完整哨兵列表(至少两个),单点哨兵宕机后无法获取新拓扑
- 别用
JedisPool替代JedisSentinelPool:前者完全不感知哨兵,切换后永远打旧地址 - 连接池复用旧连接:即使拓扑更新了,已有连接仍可能复用旧 socket,需确保
JedisSentinelPool的destroy()被真正调用
Lettuce 连接新主后仍报 READONLY 怎么办?
Lettuce 默认启用后台拓扑刷新,但它的 StatefulRedisMasterSlaveConnection 是长生命周期对象,应用层若缓存了已关闭的 StatefulRedisConnection 实例,就会继续往旧主发命令。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- URI 必须用
redis-sentinel://sentinel1:26379,sentinel2:26379/mymaster?timeout=2000格式,不能拼成普通 redis:// - 检查
ClientResources是否禁用了discovery:比如显式调用了Builder#disableTopologyRefresh() - 每次执行命令前,建议加兜底校验:
connection.sync().ping(),失败则RedisClient.connect()重建 - 避免在 Spring Bean 中长期持有
StatefulRedisConnection,应按需获取、用完释放
Resque 任务队列如何扛住哨兵切换?
Resque 自带重连逻辑,但默认只对连接中断做响应,不主动感知哨兵拓扑变更,所以切换瞬间仍可能丢任务或卡住 worker。
- 必须显式配置
reconnect_attempts: 3和timeout: 5,URL 方式也支持参数:redis://redis-sentinel.example.com:26379/0?reconnect_attempts=3&timeout=5 - worker 启动和子进程退出后会自动触发
reconnect(),但处理中遇到Redis::BaseConnectionError才会进重试循环 - 关键点:Resque 不识别
+switch-master,所以依赖底层 Redis client(如 redis-rb)是否支持哨兵自动发现;建议搭配redis-rb >= 4.8并启用sentinels参数 - 生产环境务必开启
BACKGROUND模式,否则重连逻辑不会在 worker 启动时生效
DNS 缓存和连接池残留是最大隐形坑
哪怕 SDK 正确拿到了新主 IP,应用仍可能因底层复用旧连接失败——这不是 SDK bug,是 JVM / OS / 应用层缓存链路太长。
- Java 应用检查
sun.net.inetaddr.ttlJVM 参数,默认-1(永久缓存 DNS),建议启动时加-Dsun.net.inetaddr.ttl=30 - 或代码中调用
InetAddress#setCachePolicy(30)动态控制 - 连接池未清空:JedisSentinelPool 在收到
+switch-master后会 destroy 当前池,但若应用层自己维护了额外连接池(比如 HikariCP 套了一层),就得手动清理 - 最直接验证方式:在业务代码里打点输出
pool.getCurrentHostMaster()(Jedis)或connection.getPartitions().get(0).getUri()(Lettuce),别只信日志里的 “switch-master”










