相关版本说明
服务端:
redis_version: 6.2.8
客户端:
springBoot: 2.7.7
jedis: 3.8.0
问题
偶发redis连接超时,刷新就又好了,服务日志错误信息如下:
JedisConnectionException: Unexpected end of stream.
原因
服务端连接已超时,但是客户端不知道,去使用时就会报错;
排查
redis服务端配置查看:
# 直接查看配置文件(默认为0,单位:秒, 0表示不设置超时时间):
[root@vm bin]# cat ./redis.conf | grep timeout
timeout 600
# 登录客户端查看当前配置(单位:秒)
127.0.0.1:6379> config get timeout
1) "timeout"
2) "600"
发现服务端设置了连接超时时间为10分钟。
redis默认是不设置超时时间的,也就是 timeout 0
可能是考虑到这样就有可能导致连接回收不及时,最后连接数不够用;
所以项目上设置了10分钟超时,但是没考虑到客户端,所以就出现上面这个偶发的现象了;
解决方案
方案1、redis保持默认配置,不设置超时时间,然后最大连接数设置大一些;
方案2、redis客户端配置一下连接是否超时的检测;
方案1比较简单,直接修改配置就行,但是项目上既然要配置超时时间,那只能看看方案2了。
方案二具体配置
需要手动注入,新增配置类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.data.redis.JedisClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.util.ClassUtils;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
@Configuration
public class JedisPoolConfiguration implements JedisClientConfigurationBuilderCustomizer {
@Autowired
private RedisProperties properties;
private static final boolean COMMONS_POOL2_AVAILABLE = ClassUtils.isPresent("org.apache.commons.pool2.ObjectPool",
JedisPoolConfiguration.class.getClassLoader());
/**
* 连接的最小空闲时间,达到此值后空闲连接将被移除, 默认:30分钟
*/
@Value(value = "${spring.redis.jedis.pool.minEvictableIdleTime:30M}")
private Duration minEvictableIdleTime;
/**
* 做空闲连接检测时,每次的采样数,默认每次只检测3个
*/
@Value(value = "${spring.redis.jedis.pool.numTestsPerEvictionRun:3}")
private int numTestsPerEvictionRun;
/**
* 从连接池获取连接时是否先检测(ping),每次执行命令多执行一次ping, 默认:false
*/
@Value(value = "${spring.redis.jedis.pool.testOnBorrow:false}")
private boolean testOnBorrow;
@Override
public void customize(JedisClientConfiguration.JedisClientConfigurationBuilder clientConfigurationBuilder) {
RedisProperties.Pool pool = properties.getJedis().getPool();
if (isPoolEnabled(pool)) {
applyPooling(pool, clientConfigurationBuilder);
}
}
private void applyPooling(RedisProperties.Pool pool,
JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
builder.usePooling().poolConfig(jedisPoolConfig(pool));
}
private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(pool.getMaxActive());
config.setMaxIdle(pool.getMaxIdle());
config.setMinIdle(pool.getMinIdle());
if (pool.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
}
if (pool.getMaxWait() != null) {
config.setMaxWait(pool.getMaxWait());
}
if (minEvictableIdleTime != null){
config.setMinEvictableIdleTime(minEvictableIdleTime);
}
config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
config.setTestOnBorrow(testOnBorrow);
return config;
}
protected boolean isPoolEnabled(RedisProperties.Pool pool) {
Boolean enabled = pool.getEnabled();
return (enabled != null) ? enabled : COMMONS_POOL2_AVAILABLE;
}
}
配置文件增加如下配置
spring:
redis:
jedis:
pool:
# 从连接池获取连接时是否先检测(ping),每次执行命令多执行一次ping
testOnBorrow: true
# 多长时间检测一次(-1表示不进行定时检测)
timeBetweenEvictionRuns: 30S
# 连接的最小空闲时间,达到此值后空闲连接将被移除, 默认30分钟
minEvictableIdleTime: 5M
# 做空闲连接检测时,每次的采样数,默认每次只检测3个
numTestsPerEvictionRun: 3
- 其实只需要配置testOnBorrow=true就可以,但是如果发现失效连接,则又要去重新获取一个连接,可能会费时一点;
- 所以还可以配置主动检测:定时去检测连接的配置,可以根据自己项目的实际超时时间,和连接数,在超时之前可以把所有连接都扫描检测到即可
(这里是我个人的配置,仅供参考)
至此,问题已得到解决,但为什么不能直接通过配置文件自动注入呢?感兴趣的小伙伴可以继续看看以下内容:
说明
- 因为spring-boot-autoconfigure中,不支持这三个配置:
testOnBorrow minEvictableIdleTime numTestsPerEvictionRun
- 详细可以查看源码:
org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool
org.springframework.boot.autoconfigure.data.redis.JedisConnectionConfiguration#jedisPoolConfig - 所以可以把源码复制出来,自己修改一下
- 该问题已经反馈给spring-boot:
spring-projects/spring-boot/issues/33814