ITPub博客

首页 > 数据库 > NoSQL > redis cluster节点故障后Lettuce客户端无法自动恢复的解决方案

redis cluster节点故障后Lettuce客户端无法自动恢复的解决方案

原创 NoSQL 作者:贺子_DBA时代 时间:2026-07-20 22:48:40 0 删除 编辑

一、背景

在微服务架构中,Redis Cluster凭借其高可用、数据分片的特性,成为缓存层的首选方案。然而,我们在生产环境中发现了一个棘手的问题: 当Redis集群在发生主从切换或某个节点宕机时,基于Spring Boot 2.x的应用会陷入“连接拒绝”的异常泥潭,业务操作被阻塞,无法自动恢复。本文将从问题现象、根因分析到多种解决方案,完整记录这一问题的排查与解决过程。

二、问题现象

应用日志报错1:Lettuce 尝试连接这个IP,但连不上

io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: /10.2.1.119:7000 Caused by: java.net.ConnectException: Connection refused

应用日志报错2:Lettuce 认识这个IP,但拓扑视图里"不认

"2026-07-02 20:12:39.191 ERROR 1 [http-nio-40080-exec-7] [TID: cdca4378-bf8d-4e3a-bbb2-ad8809255aa2] UserExceptionHandler.exceptionHandler():96 - 服务端异常,异常信息={} org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: io.lettuce.core.cluster.UnknownPartitionException: Connection to 10.2.1.119:7000 not allowed. This partition is not known in the cluster view.

三、根因分析

3.1 Jedis vs Lettuce

Spring Boot 2.x开始,默认的Redis客户端从 Jedis切换为 Lettuce。两者在集群拓扑刷新机制上存在关键差异:
特性 Jedis Lettuce
集群拓扑刷新 默认自动支持 (通过异常反馈触发重连和刷新) 默认关闭 ,需手动开启
连接模型 阻塞式I/O 异步非阻塞(基于Netty)
Spring Boot 1.x 默认使用 -
Spring Boot 2.x - 默认使用

3.2 问题本质

Lettuce客户端在初始化时缓存了集群的拓扑信息(节点列表、slot分配等)。当7000节点宕机后:
  1. Redis集群已完成故障转移,7005成为新master
  2. 但Lettuce客户端的拓扑视图 未更新,仍认为7000是master
  3. 操作 k1(slot在7000范围内)时,Lettuce继续尝试连接7000
  4. 连接失败后,客户端未触发拓扑刷新,陷入无限重试
根本原因:Lettuce的集群拓扑动态刷新功能 默认未开启,而Spring Boot在集成时也未主动开启。

四、解决方案

解决方案1:升级到 Spring Boot 2.3.0+(推荐)

Spring Boot 2.3.0开始,官方增加了对Lettuce拓扑刷新的配置支持。

步骤一:升级Spring Boot版本至2.3.0或更高。

步骤二:添加配置:

spring:  redis:    timeout: 60s    lettuce:      cluster:        refresh:          adaptive: true          # 开启自适应刷新          period: 60s             # 周期刷新间隔        

说明:

adaptive=true:根据 MOVED、 ASK、 PERSISTENT等事件触发刷新                              

period:定时刷新拓扑,作为兜底策略                              

dynamic-refresh-sources(Spring Boot 2.4.0+):决定刷新时只查初始种子节点(false)还是所有已发现节点(true)

adaptive=true 具体触发场景

Lettuce 的 AdaptiveRefreshTrigger 会在以下情况触发拓扑刷新:
  1. MOVED 重定向
    • 客户端收到 MOVED slot host:port 响应时
    • 说明槽位已迁移,需要重新获取节点映射
  1. ASK 重定向
    • 收到 ASK 响应时(槽位正在迁移中)
    • 需要刷新以获取最新的迁移状态
  1. 连接断开/不可达
    • 与某个节点的连接断开( ConnectionWatchdog 触发)
    • 节点被判定为不可达时
  1. PERSISTENT 重连失败
    • 连接断开后重试多次仍无法恢复
  1. UNCOVERED_SLOT / CLUSTERDOWN
    • 收到 CLUSTERDOWN 错误(集群处于下线状态)
    • 发现未覆盖的槽位( UNCOVERED_SLOT)
  1. READONLY 模式变更
    • 从节点状态变化(如主从切换后)

关键源码逻辑(Lettuce)

// 触发条件枚举 enum Event {    MOVED,           // MOVED 重定向    ASK,             // ASK 重定向    UNCOVERED_SLOT,  // 槽位未覆盖    CLUSTERDOWN,     // 集群下线    CONNECT,         // 新连接建立(可选)    DISCONNECT,      // 连接断开    PERSISTENT_RECONNECTS // 持续重连失败

解决方案2:通过代码配置LettuceConnectionFactory

适用于无法升级Spring Boot版本,或需要更精细控制的场景。

@Configuration public class LettuceRedisConfig {    @Bean    public DefaultClientResources lettuceClientResources() {        return DefaultClientResources.create();    }    @Bean    public LettuceConnectionFactory lettuceConnectionFactory(            RedisProperties redisProperties,            ClientResources clientResources) {        // 1. 配置拓扑刷新策略        ClusterTopologyRefreshOptions topologyRefreshOptions =            ClusterTopologyRefreshOptions.builder()                .enablePeriodicRefresh(Duration.ofSeconds(30))   // 周期刷新                .enableAllAdaptiveRefreshTriggers()              // 自适应刷新(全部触发源)                .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))                .build();        // 2. 配置客户端选项        ClusterClientOptions clusterClientOptions =            ClusterClientOptions.builder()                .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(10)))                .topologyRefreshOptions(topologyRefreshOptions)                .build();        // 3. 构建客户端配置        LettuceClientConfiguration clientConfiguration =            LettuceClientConfiguration.builder()                .clientResources(clientResources)                .clientOptions(clusterClientOptions)                .build();        // 4. 构建集群配置        RedisClusterConfiguration clusterConfig =            new RedisClusterConfiguration(redisProperties.getCluster().getNodes());        clusterConfig.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());        clusterConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));        return new LettuceConnectionFactory(clusterConfig, clientConfiguration);    }

}

高级版配置(含连接池)

@Bean(destroyMethod = "destroy") public LettuceConnectionFactory lettuceConnectionFactory() {    // 拓扑刷新配置    ClusterTopologyRefreshOptions topologyRefreshOptions =        ClusterTopologyRefreshOptions.builder()            .enableAllAdaptiveRefreshTriggers()            .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))            .enablePeriodicRefresh(Duration.ofSeconds(20))            .build();    // 客户端选项    ClientOptions clientOptions = ClusterClientOptions.builder()        .topologyRefreshOptions(topologyRefreshOptions)        .build();    // 连接池配置    LettuceClientConfiguration clientConfig =        LettucePoolingClientConfiguration.builder()            .poolConfig(genericObjectPoolConfig())            .clientOptions(clientOptions)            .commandTimeout(redisProperties.getTimeout())            .build();    // 集群节点配置    RedisClusterConfiguration clusterConfig = buildClusterConfiguration();    return new LettuceConnectionFactory(clusterConfig, clientConfig); }

解决方案3:回退使用Jedis

适合不想折腾配置,或对Lettuce不熟悉的团队。

<dependency>

   <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId>    <exclusions>        <exclusion>            <groupId>io.lettuce</groupId>            <artifactId>lettuce-core</artifactId>        </exclusion>    </exclusions> </dependency> <dependency>    <groupId>redis.clients</groupId>    <artifactId> jedis</artifactId> </dependency>

解决方案4:旧版本Spring Boot的兼容方案

对于Spring Boot 2.3.0之前的版本,可以参考方案2中开启 adaptive刷新的逻辑,手动配置 LettuceConnectionFactory。核心要点:
  • 必须开启 自适应刷新( enableAllAdaptiveRefreshTriggers())
  • 建议同时开启 周期刷新( enablePeriodicRefresh())作为兜底
  • 刷新频率不宜过高(建议10-60秒),避免对集群造成压力

五、配置项速查表

配置项 数据类型 默认值 说明
spring.redis.lettuce.cluster.refresh.adaptive boolean false 开启自适应拓扑刷新
spring.redis.lettuce.cluster.refresh.period Duration - 周期刷新间隔
spring.redis.timeout Duration 60s 命令超时时间
spring.redis.lettuce.pool.maxActive int 8 连接池最大连接数
spring.redis.lettuce.pool.maxIdle int 8 连接池最大空闲数
spring.redis.lettuce.pool.minIdle int 0 连接池最小空闲数
spring.redis.lettuce.shutdownTimeout Duration 100ms 关机超时时限

六、总结与建议

方案 适用场景 优点 缺点
升级到Spring Boot 2.3.0+ 新项目或可升级版本 配置简单,官方支持 需升级框架版本
代码配置Lettuce 无法升级版本 灵活可控,功能完整 代码量较大
回退Jedis 快速修复 零配置,开箱即用 性能略逊于Lettuce
最佳实践建议:
  1. 生产环境务必开启Lettuce拓扑刷新:无论通过配置还是代码,确保 adaptive=true
  2. 同时配置周期刷新作为兜底:防止自适应刷新因某些原因未能触发
  3. 合理设置刷新频率:生产环境建议30-60秒,避免过高频率压垮集群
  4. 升级到Spring Boot 2.3.0+:享受官方原生支持,减少维护成本



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/29654823/viewspace-3176337/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
10年数据库管理员从业经历,熟悉Oracle、mysql、tidb、oceanbase、redis、 mongodb 、python等技术栈,持有11g ocm 、mysql 5.7 ocp 、tidb 5.0 ptcp、tidb 6.0 ptca、oceanbase obcp等

注册时间:2014-05-12

  • 博文量
    327
  • 访问量
    2148946