Redis堵塞获取时报命令超时Command timed out after 1 minute(s)

环境

pom.xml

SpringBoot2.3.x
Redis
Lettuce
复制代码

客户端

Redis 5.0.x
复制代码

最近在搞一个Redis(List结构)做轮询队列的时候发现的问题、由于数据较少且长时间没有数据进入队列、我采用的是堵塞形式获取队列中的值、因此后续发生了Command timed out after 1 minute(s)命令超时异常、于是我配置了yaml中的redis配置的timeout的时间、后面就是我配多少时间、他就什么时候报这个命令超时异常、于是走投无路的情况下打开的我万能的百度、一番搜索好家伙、各种说法、甚至一模一样、这不是最主要的、主要的是我按照他们的解决方式都试了一下、还是一样不能解决这个问题、就一个靠谱的就是把Lettuce连接池换成jedis但是还是想用Lettuce怎么样。

异常信息

image.png

官网的提出来的方法、可以进去看看

善良的我帮你翻译好了

image.png

知道我看到了这句、TimeoutOptions使用自定义配置TimeoutSource、首先想到了百度、但是都没人配置过、我都不知道往什么地方给他注入塞进去、后面看源码、以及百度看别人配置的LettuceConnectionFactory 中可以配置。

@Configuration
public class RedisFactory {

    RedisStandaloneConfiguration connection(RedisProperties redisProperties) {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        // 这些都是一些yaml中配置的
        redisStandaloneConfiguration.setHostName(redisProperties.getHost());
        redisStandaloneConfiguration.setPort(redisProperties.getPort());
        redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase());
        redisStandaloneConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        return redisStandaloneConfiguration;
    }

    @Bean
    LettuceConnectionFactory lettuceConnectionFactory(RedisProperties redisProperties) {
        RedisProperties.Pool pool = redisProperties.getLettuce().getPool();
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(pool.getMaxIdle());
        poolConfig.setMaxTotal(pool.getMaxActive());
        poolConfig.setMinIdle(pool.getMinIdle());
        LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                .poolConfig(poolConfig) // 下面这些才是主要
                .clientOptions(ClusterClientOptions.builder().timeoutOptions(TimeoutOptions.builder().timeoutSource(new TimeoutOptions.TimeoutSource() {
                    @Override
                    public long getTimeout(RedisCommand<?, ?, ?> command) {
                        // 指定到某一个命令类型
                        if (command.getType() == CommandType.BRPOP) {
                            // 指定命令类型的命令超时时间
                            return 0;
                        }
                        // -1的话会按照默认的给超时时间
                        return -1;
                    }
                }).build()).build())
                .build();

        return new LettuceConnectionFactory(connection(redisProperties), lettuceClientConfiguration);
    }
}
复制代码

这种方式可以解决命令超时的异常、但是我个人感觉会有问题、因为本来LettuceConnectionFactory由自动配置来进行初始配置的、但是我自己注入了这个bean就会用我们自己写的LettuceConnectionFactory这个bean、可能会导致一些配置会失效出现问题、所以不建议你们线上环境来进行修改!!!

来看一下源码吧LettuceConnectionConfiguration

@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory(
      ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
      ClientResources clientResources) throws UnknownHostException {
   LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
         getProperties().getLettuce().getPool());
   return createLettuceConnectionFactory(clientConfig);
}
复制代码

getLettuceClientConfiguration()方法

private LettuceClientConfiguration getLettuceClientConfiguration(
      ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
      ClientResources clientResources, Pool pool) {
   LettuceClientConfigurationBuilder builder = createBuilder(pool);
   applyProperties(builder);
   if (StringUtils.hasText(getProperties().getUrl())) {
      customizeConfigurationFromUrl(builder);
   }
// 可以到这个地方、设置了一个默认的timeoutOptions、就是根据timeout的时间来控制所有堵塞命令的超时时间、timeout时间一到就报命令超时
builder.clientOptions(initializeClientOptionsBuilder().timeoutOptions(TimeoutOptions.enabled()).build());
   builder.clientResources(clientResources);
   builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
   return builder.build();
}
复制代码

例子:轮询这个方法、有数据就弹出。

RedisUtil.lBRightPop(QUEUE_MESSAGE, 0, TimeUnit.SECONDS);
复制代码

你们看debug 一步一步进去看执行流程 image.png LettuceListCommands 类子的bRPop方法、中调用了一个由代理实现的bRPop方法

image.png

image.png

这边红框住的就是一个获取命令超时时间的方法

image.png

image.png

state.timeoutSource.getTimeout(command) 这个getTimeout()是调用我们上面配置的LettuceConnectionFactory中具体的getTimeout()方法实现、如果返回的值给了timeoutNs、大于等于0的情况下就是使用timeoutNs做为命令超时时间、否则就是拿defaultTimeoutSupplier.getAsLong();debug进入方法是下面这个地方connection.getTimeout().toNanos()去拿到yaml中配置的timeout时间、默认是60秒也就是一分钟。 image.png

上面这些这是说了一些命令为什么根据timeout的时间一到就报错、以上就讲了获取命令的超时时间的一个说明、后续的里面的代码可执行debug

总结

  • 解决方法
    • 换jedis连接池可以解决
    • 按我上面重新去注入一个LettuceConnectionFactory bean也可以解决
  • 注意如果不是线上环境、可以按照我的这种方式来解决、你们公司线上环境还是不能瞎搞的!!!(万一崩了别找我麻烦)
  • 为什么不让线上替换这个、因为业务量大的公司一般有Redis集群的配置、别被你们换了LettuceConnectionFactory给程序搞挂了。(我自己设想、因为我这边没有搞集群的配置、所以不是很清楚、其他还请自行看源码配置一下)

线上环境也能想一个完美的方式来解决、根据你们自己的业务量来修改合适的timeout的时间、业务量是指你们多久可以从堵塞状态拿到一条数据来决定、就好比10分钟内能从堵塞队列中拿到一条数据、你timeout就可以设置30分钟、或者1小时等......

避坑:我自己测试时在报Command timed out after 1 minute(s)错误后、后面来了两条数据时、第一条不会被弹出、第二条正常弹出数据。有问题可以找我一起讨论!!!

吐槽

为什么我百度上的方式我试了几个都没用、就换jedis连接池这个可以解决、还有一个是什么定时调用一个验证连接是否断开的这种方法(类似于发送心跳包的这种)、我不知道是不是我环境有问题他们写方式没一个能用、解决方式一堆、好不容易换一个高级一点的lettuce又来一波逆向操作给换回jedis、哈哈哈绝了!!!

每日一汤

美丽的风景、不至有一天有、而你只需要把这些不同的风景用相机记录下来。 美丽的风景、不至有一天有、而你只需要把这些不同的风景用相机记录下来。

おすすめ

転載: juejin.im/post/7034521027786833927