Redis哨兵模式本身不提供读写分离能力,客户端必须自行实现角色识别与请求路由,Lettuce的REPLICA_PREFERRED仅在连接建立后生效,且依赖哨兵拓扑发现与持续角色感知,常见失效源于启动时获取旧地址、未过滤断连从节点、连接池未及时剔除过期连接等,需结合StaticMasterReplicaTopologyProvider、主动健康检查及事件监听等手动协同设计。

Redis 哨兵模式本身不提供读写分离能力,客户端必须自己承担角色识别与请求路由——这不是配置开关能解决的问题,而是连接初始化、节点发现、故障响应三个环节的协同设计。
为什么 Lettuce 的 ReadFrom.REPLICA_PREFERRED 不是开箱即用
这个策略只在连接建立后生效,但前提是客户端已通过哨兵获取到最新拓扑,并且能持续感知主从角色变化。常见失效场景包括:
- 应用启动时哨兵尚未完成故障转移,
LettuceClientConfigurationBuilderCustomizer读取到的是旧主节点地址,后续所有连接都发往已下线节点 - 哨兵返回的从节点中存在
flags=slave,disconnected,sdown,但 Lettuce 默认不主动过滤,仍可能把读请求打到不可用从库 - 未配置
clientName或未启用ping-before-activate,导致连接复用时复用了已断连的 socket
实操建议:必须配合 StaticMasterReplicaTopologyProvider + 自定义健康检查逻辑,不能只依赖 readFrom() 单一配置。
Spring Boot 中 spring.redis.sentinel 配置的隐藏陷阱
YAML 里只写哨兵地址和 master 名称看似简洁,但容易忽略两个关键点:
-
spring.redis.password是给主/从节点用的,而哨兵节点(26379端口)若设置了requirepass,Lettuce 默认不会透传认证,需额外配置sentinelPassword(Lettuce 6.2+ 支持) - 当哨兵集群启用了 ACL(如
SENTINEL AUTH mymaster),仅靠sentinelPassword不够,必须在LettureClientConfigurationBuilderCustomizer中显式调用builder.sentinelAuthentication(...) - 未设置
lettuce.pool.time-between-eviction-runs,导致连接池长期持有对已切换主节点的过期连接,引发READONLY错误
如何让读请求真正避开正在同步的从节点
哨兵返回的 SENTINEL slaves mymaster 结果里,每个从节点带 lag 字段,但 Lettuce 不解析它。你需要:
- 在自定义
RedisClient初始化时,通过SentinelCommands主动拉取从节点列表,并过滤lag > 100的节点(业务容忍延迟的阈值) - 监听哨兵的
+slave和-slave事件(用pubsub订阅),而非轮询,避免因 polling 间隔导致读流量短暂打到新上线但尚未同步完成的从库 - 对每个从节点连接启用
pingBeforeActivateConnection = true,并在PingCommand失败时触发连接剔除,而不是等首次读请求失败才重试
这一步绕不开手动干预——Lettuce 提供的是能力基座,不是全自动路由引擎。
Proxy 层才是动态扩容的现实解法
如果你需要频繁增减从节点、或要求读请求零感知故障转移,客户端方案会越来越重。此时应承认一个事实:Lettuce 的哨兵支持适合中小规模稳定拓扑;一旦涉及动态扩缩容,就必须引入 Proxy。
关键判断点:
- 是否要求新增从节点后,5 秒内读流量自动分发过去?——客户端做不到,Proxy 可以通过
SENTINEL subscribe实时响应+slave事件 - 是否要屏蔽
LOADING Redis is loading the dataset in memory这类从节点启动中的临时错误?——客户端需自己 catch 并重试,Proxy 可统一拦截并降级到其他节点 - 是否已有统一网关层?——直接在网关里集成哨兵监听比每个 Java 应用单独维护一套更可控
最易被忽略的是:哨兵的 down-after-milliseconds 和 Proxy 的健康检查周期必须错开,否则可能因哨兵尚未标记节点为 sdown,Proxy 却已将其踢出读池,造成读节点数骤减。











