springboot结合Redis的Lettuce响应池连接错误:io.lettuce.core.RedisCommandTimeoutException:Commandtimedout

原创
小哥 3年前 (2022-11-08) 阅读数 6 #大杂烩

1.最大可能连接/超时设置得太小。

如图所示,检查最大连接数和超时时间并将其打开。

redis:
    lettuce:
      pool:
        MaxTotal: 50 #最大连接数
        minIdle: 1
        maxWaitMillis: 5000
        maxIdle: 5
        testOnBorrow: true
        testOnReturn: true
        testWhileIdle: true
    redis-a:
      database: 8
      hostName: 127.0.0.1
      port: 6379
      timeout: 5000 #超时时间
    redis-b:
      database: 8
      hostName: 172.16.35.60
      port: 31105
      password: tenxcloud
      timeout: 5000 #超时时间

2.链接池中的链接断开

这个问题有两个原因:
1、Lettuce 自适应拓扑刷新(Adaptive updates)具有定时拓扑刷新(Periodic updates) 默认情况下处于禁用状态。
2,使用的项目k8s做docker集装箱化部署,k8s如果设置空闲连接超时,则会断开连接,因此当从连接池中获取断开连接时,将报告错误。

解决方案是一致的:

方案1:每个环节的自动验证(pro-test易于使用)

说明:lettuce提供了一种检查连接的方法,lettuce提供验证连接的方法。 只是它在默认情况下没有打开。
如果您打开它,您将在每次获得连接时进行检查,并打开获取连接的检查。

@Component
@Slf4j
public class LettuceConnectionValidConfig implements InitializingBean {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Override
    public void afterPropertiesSet() throws Exception {
        if(redisConnectionFactory instanceof LettuceConnectionFactory){
            LettuceConnectionFactory c=(LettuceConnectionFactory)redisConnectionFactory;
            c.setValidateConnection(true);
        }
    }
}

方案2:定时任务

描述:每2第二次检查异常lettuce连接是否正常,解决长期闲置lettuce连接已关闭netty无法及时监控的问题。

@Component
@Slf4j
public class LettuceConnectionValidTask   {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Scheduled(cron="0/2 * * * * ?")
    public void task() {
        if(redisConnectionFactory instanceof LettuceConnectionFactory){
            LettuceConnectionFactory c=(LettuceConnectionFactory)redisConnectionFactory;
            c.validateConnection();
        }
    }
}

方案三:springboot2.3在上述版本中,可以添加配置分辨率。

# 解决redis 240第二个超时问题
    lettuce:
      cluster:
        refresh:
          adaptive: true
          period: 20

方案4:排除lettuce,采用jedis。

        
            org.springframework.boot
            spring-boot-starter-data-redis
            
                
                    io.lettuce
                    lettuce-core
                
            
        

        
            redis.clients
            jedis
        

请注意,配置文件放置。lettuce.pool换成jedis.pool。

场景5:重写连接工厂实例(强烈建议)。

描述:重写连接工厂实例并进行更改。LettuceClientConfiguration 打开拓扑更新。

    @Configuration
    public class RedisConfig {

        @Autowired
        private RedisProperties redisProperties;

        //这是一个固定模板。
        //我定义了一个RedisTemplate
        @Bean
        @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(@Qualifier("lettuceConnectionFactoryUvPv") RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);

            //Json串行化配置
            Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.activateDefaultTyping(om.getPolymorphicTypeValidator());
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            //解决序列化问题
            om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            jackson2JsonRedisSerializer.setObjectMapper(om);

            //String的序列化
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

            //key采用String如何序列化
            template.setKeySerializer(stringRedisSerializer);
            //hash的key也采用String如何序列化
            template.setHashKeySerializer(stringRedisSerializer);

            //value采用序列化方法。jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);

            //hash的value采用序列化方法。jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();

            return template;
        }

        /**
         * 为RedisTemplate配置Redis连接工厂实施
         * LettuceConnectionFactory实现了RedisConnectionFactory接口
         * UVPV用Redis
         *
         * @return 返回LettuceConnectionFactory
         */
        @Bean(destroyMethod = "destroy")
        //重要的是要在构建中注意这一点。LettuceConnectionFactory 如果不使用内置destroyMethod,可能导致Redis连接早于其他Bean被销毁
        public LettuceConnectionFactory lettuceConnectionFactoryUvPv() throws Exception {

            List<String> clusterNodes = redisProperties.getCluster().getNodes();
            Set<RedisNode> nodes = new HashSet<>();
            clusterNodes.forEach(address -> nodes.add(new RedisNode(address.split(":")[0].trim(), Integer.parseInt(address.split(":")[1]))));
            RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
            clusterConfiguration.setClusterNodes(nodes);
            clusterConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
            clusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());

            RedisStandaloneConfiguration  redisStandaloneConfiguration=new RedisStandaloneConfiguration();
            redisStandaloneConfiguration.setHostName(redisProperties.getHost());
            redisStandaloneConfiguration.setPassword(redisProperties.getPassword());
            redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase());
            redisStandaloneConfiguration.setPort(redisProperties.getPort());

            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
            poolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
            poolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
            poolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());

            return new LettuceConnectionFactory(redisStandaloneConfiguration, getLettuceClientConfiguration(poolConfig));
        }

        /**
         * 配置LettuceClientConfiguration 包括线程池配置和安全项配置
         *
         * @param genericObjectPoolConfig common-pool2线程池
         * @return lettuceClientConfiguration
         */
        private LettuceClientConfiguration getLettuceClientConfiguration(GenericObjectPoolConfig genericObjectPoolConfig) {
            /*
            ClusterTopologyRefreshOptions该配置用于打开自适应刷新和定时刷新。如果自适应刷新未打开,Redis群集更改将导致连接异常!
             */
            ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                    //启用自适应刷新
                    //.enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT, ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS)
                    //打开所有自适应刷新,MOVED,ASK,PERSISTENT都会触发
                    .enableAllAdaptiveRefreshTriggers()
                    // 自适应刷新超时(默认30秒)
                    .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(25)) //关闭和打开后的默认时间为30秒
                    // 打开循环刷新
                    .enablePeriodicRefresh(Duration.ofSeconds(20))  // 关闭和打开后的默认时间为60秒 ClusterTopologyRefreshOptions.DEFAULT_REFRESH_PERIOD 60  .enablePeriodicRefresh(Duration.ofSeconds(2)) = .enablePeriodicRefresh().refreshPeriod(Duration.ofSeconds(2))
                    .build();
            return LettucePoolingClientConfiguration.builder()
                    .poolConfig(genericObjectPoolConfig)
                    .clientOptions(ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build())
                    //将appID进线连接,方便Redis在监视中查看
                    //.clientName(appName + "_lettuce")
                    .build();
        }
    }

重写连接工厂实例(强烈建议)。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除