环境
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
怎么样。
异常信息
善良的我帮你翻译好了
知道我看到了这句、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 一步一步进去看执行流程
LettuceListCommands
类子的bRPop
方法、中调用了一个由代理实现的bRPop
方法
这边红框住的就是一个获取命令超时时间的方法
state.timeoutSource.getTimeout(command)
这个getTimeout()
是调用我们上面配置的LettuceConnectionFactory
中具体的getTimeout()
方法实现、如果返回的值给了timeoutNs、大于等于0的情况下就是使用timeoutNs
做为命令超时时间、否则就是拿defaultTimeoutSupplier.getAsLong();
debug进入方法是下面这个地方connection.getTimeout().toNanos()
去拿到yaml中配置的timeout时间、默认是60秒也就是一分钟。
上面这些这是说了一些命令为什么根据timeout
的时间一到就报错、以上就讲了获取命令的超时时间的一个说明、后续的里面的代码可执行debug
总结
- 解决方法
- 换jedis连接池可以解决
- 按我上面重新去注入一个
LettuceConnectionFactory
bean也可以解决
- 注意如果不是线上环境、可以按照我的这种方式来解决、你们公司线上环境还是不能瞎搞的!!!(万一崩了别找我麻烦)
- 为什么不让线上替换这个、因为业务量大的公司一般有
Redis
集群的配置、别被你们换了LettuceConnectionFactory
给程序搞挂了。(我自己设想、因为我这边没有搞集群的配置、所以不是很清楚、其他还请自行看源码配置一下)
线上环境也能想一个完美的方式来解决、根据你们自己的业务量来修改合适的timeout
的时间、业务量是指你们多久可以从堵塞状态拿到一条数据来决定、就好比10分钟内能从堵塞队列中拿到一条数据、你timeout就可以设置30分钟、或者1小时等......
避坑:我自己测试时在报Command timed out after 1 minute(s)
错误后、后面来了两条数据时、第一条不会被弹出、第二条正常弹出数据。有问题可以找我一起讨论!!!
吐槽
为什么我百度上的方式我试了几个都没用、就换jedis
连接池这个可以解决、还有一个是什么定时调用一个验证连接是否断开的这种方法(类似于发送心跳包的这种)、我不知道是不是我环境有问题他们写方式没一个能用、解决方式一堆、好不容易换一个高级一点的lettuce
又来一波逆向操作给换回jedis
、哈哈哈绝了!!!
每日一汤