升级至Spring Boot 3.2.2后Redis命令中断异常排查求助
问题描述
将应用从Spring Boot 2.7升级至3.2.2后,在高负载环境下间歇性出现org.springframework.data.redis.RedisSystemException: Redis command interrupted异常。该问题随机发生,四天内仅在20分钟内出现过两次。相同代码在Java 8 + Spring Boot 2.7环境下运行完全正常。
异常栈信息
org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert (LettuceExceptionConverter.java:60)[-] org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert (LettuceExceptionConverter.java:41) org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate (PassThroughExceptionTranslationStrategy.java:40) org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate (FallbackExceptionTranslationStrategy.java:38) org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException (LettuceConnection.java:306) org.springframework.data.redis.connection.lettuce.LettuceConnection.await (LettuceConnection.java:1010) org.springframework.data.redis.connection.lettuce.LettuceConnection.lambda$ <unresolved>.<unresolved> (<unresolved>) org.springframework.data.redis.connection.lettuce.LettuceInvoker$Synchronizer.invoke (LettuceInvoker.java:665)[-] org.springframework.data.redis.connection.lettuce.LettuceInvoker.just (LettuceInvoker.java:94) org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get (LettuceStringCommands.java:54) org.springframework.data.redis.cache.DefaultRedisCacheWriter.lambda$ <unresolved>.<unresolved> (<unresolved>) org.springframework.data.redis.cache.DefaultRedisCacheWriter.execute (DefaultRedisCacheWriter.java:348)[-] org.springframework.data.redis.cache.DefaultRedisCacheWriter.get (DefaultRedisCacheWriter.java:140) org.springframework.data.redis.cache.DefaultRedisCacheWriter.get (DefaultRedisCacheWriter.java:129) org.springframework.data.redis.cache.RedisCache.lookup (RedisCache.java:207) org.springframework.cache.support.AbstractValueAdaptingCache.get (AbstractValueAdaptingCache.java:58)
相关代码实现及配置
RedisCacheManager cacheManager; public Object getObject(String key){ // 异常发生在这里 Cache.ValueWrapper v=cacheManager.getCache(CACHE_NAME_PREFIX).get(key); return v!=null?v.get():null; } @Cacheable(value = CACHE_NAME_PREFIX, key = CACHE_KEY) public Object cacheObject(String cardNumber,String cacheKey){ // 业务实现....... } // 配置类 @Bean(name = {"azureRedisTemplate"}) public StringRedisTemplate azureRedisTemplate(RedisConnectionFactory redisConnectionFactory){ StringRedisTemplate template=new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(getGenericJackson2JsonRedisSerializer()); return template; } @Primary @Bean("redisCacheManager") public RedisCacheManager cacheManager(){ RedisCacheConfiguration configuration= RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds((long)this.cacheProperties.getTtl())) .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer( getGenericJackson2JsonRedisSerializer())) .serializeKeysWith( RedisSerializationContext.SerializationPair.fromSerializer( new StringRedisSerializer())); return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory( redisConnectionFactory()) .cacheDefaults(configuration) .build(); } @Bean RedisConnectionFactory redisConnectionFactory(){ LettuceConnectionFactory lettuceConnFactory=null; if(vcapServices!=null){ try{ Map<String, Object> vcap=JsonUtil.parse(vcapServices); List<Object> azureRedisCache=(List<Object>)vcap.get(redisLable); Map<String, Object> credentials= JsonUtil.toMap(JsonUtil.toMap(azureRedisCache.get(0)).get("credentials")); RedisStandaloneConfiguration standaloneConfiguration=new RedisStandaloneConfiguration(); standaloneConfiguration.setPort(Integer.parseInt(credentials.get("sslPort").toString())); standaloneConfiguration.setHostName(credentials.get("hostname").toString()); standaloneConfiguration.setPassword( RedisPassword.of(credentials.get("primaryKey").toString())); LettuceClientConfiguration lettuceClientConfiguration= LettuceClientConfiguration.builder().useSsl().disablePeerVerification().build(); lettuceConnFactory= new LettuceConnectionFactory(standaloneConfiguration,lettuceClientConfiguration); }catch(Exception e){ logger.error("Azure redis connection factory creation failed. ",e); } logger.debug("Azure redis connection factory initialized"); } return lettuceConnFactory; }
异常原因分析
1. RedisConnectionFactory实例不一致问题
你的配置中,RedisCacheManager通过直接调用redisConnectionFactory()方法创建新的连接工厂实例,而azureRedisTemplate则是注入Spring容器中已存在的RedisConnectionFactory bean。这会导致两者使用完全独立的连接池:
- Spring Boot 2.7中可能对这种重复创建的容忍度更高,或者连接池参数默认配置更宽松,未触发问题;
- 升级到3.2.2后,Lettuce客户端或Spring Data Redis的连接管理逻辑更严格,高负载下两个独立连接池的资源竞争或连接耗尽会引发命令中断异常。
2. Lettuce客户端版本升级带来的变化
Spring Boot 3.2.2对应的Lettuce版本远高于2.7(2.7用的是Lettuce 6.2.x,3.2.2用的是Lettuce 6.3.x),新版本中:
- 连接超时、命令中断的检测逻辑更敏感;
- 连接池的默认配置可能发生变化,比如最大连接数、空闲连接回收策略更严格,高负载下容易出现连接被中断的情况。
3. 高负载下的连接资源竞争
两个独立的连接池会占用更多的Redis服务器连接资源,当Redis服务器的最大连接数接近上限时,新的命令请求可能被拒绝或中断;同时客户端侧的两个连接池在高负载下也可能出现内部的连接分配冲突,导致命令执行被中断。
修复建议
- 修改
cacheManager()方法,注入Spring容器中的RedisConnectionFactorybean,而不是直接调用redisConnectionFactory()创建新实例:
@Primary @Bean("redisCacheManager") public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){ RedisCacheConfiguration configuration= RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds((long)this.cacheProperties.getTtl())) .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer( getGenericJackson2JsonRedisSerializer())) .serializeKeysWith( RedisSerializationContext.SerializationPair.fromSerializer( new StringRedisSerializer())); return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory) .cacheDefaults(configuration) .build(); }
- 检查并调整Lettuce连接池配置,比如设置合理的最大连接数、空闲超时、连接超时等参数,避免高负载下资源耗尽:
@Bean RedisConnectionFactory redisConnectionFactory(){ // ... 原有配置代码 LettuceClientConfiguration lettuceClientConfiguration= LettuceClientConfiguration.builder() .useSsl().disablePeerVerification() .clientOptions(ClientOptions.builder() .disconnectedBehavior(DisconnectedBehavior.REJECT_COMMANDS) .build()) .clientResources(ClientResources.builder() .ioThreadPoolSize(8) .computationThreadPoolSize(8) .build()) .build(); lettuceConnFactory= new LettuceConnectionFactory(standaloneConfiguration,lettuceClientConfiguration); // ... 原有配置代码 }
内容的提问来源于stack exchange,提问作者user24043407
相关产品推荐
相关产品推荐

