这里写自定义目录标题
使用jedis时遇到的坑 Socket closed PONG
最近接手一个项目,前任用的是jedis+连接池,主要用于存储token跟分布式锁用的,配置如下
@Configuration
@Slf4j
public class RedisBaseConfig {
@Value("${auth.redis.host}")
protected String host;
@Value("${auth.redis.password}")
protected String password;
@Value("${auth.redis.database}")
protected Integer database;
protected static final int PORT = 6379;
@Bean(name = "authJedisConnectionFactory")
public JedisConnectionFactory authJedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setDatabase(database);
factory.setHostName(host);
factory.setPassword(password);
factory.setPort(PORT);
factory.setTimeout(1000 * 3);
factory.setPoolConfig(poolConfig());
log.info("redis config=========" + host);
return factory;
}
private JedisPoolConfig poolConfig() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(1000);
poolConfig.setMinIdle(100);
poolConfig.setMaxWaitMillis(1000 * 60 * 3);
//11.11新增配置,解决空闲时期的连接释放
poolConfig.setTestWhileIdle(true);
poolConfig.setMinEvictableIdleTimeMillis(30 * 1000);
poolConfig.setTimeBetweenEvictionRunsMillis(60 * 1000);
poolConfig.setNumTestsPerEvictionRun(-1);
//
return poolConfig;
}
}
项目运行过程中,经常会报一个错误redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Socket closed
查了一波资料后,找到一个解决办法在链接池里配两个参数
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
配置完后确实发现Socket closed的报错少了,但是又出现个新的问题,缓存get数据竟然取到了PONG,而不是存的内容。
导致这个问题的原因是这个配置poolConfig.setTestOnBorrow(true);
这个配置表示的若开启,每一次获取redis实例时会先ping一下redis,才进行命令的发送,并且出现了有get命令直接返回PONG
开启了检查是否可用时在引入每个redis时都会发送ping命令,造成效率较低,在有一定并发量的时候通过看redis监控发现在返回PONG时,redis后台服务器此时接收到两个接连的ping命令,导致将PONG返回给了get命令,并且接口耗时比较高。
去掉该配置后:接口耗时减少一半,不再出现get命令返回PONG的情况,但并发量大时也会出现socket closed的错误(寻找答案中)
目前处理,把jedis升到最高版本
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
并且配置类需要改变下,因为之前的写法一直提示过时了,另外这个配置类目前发现有两种写法,一种是通过工厂类来获取实例的,一种是直接通过连接池获取的
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @program: gw-bff
* @description:
* @author: maliang
* @create: 2022/01/05 15:23
*/
@Configuration
@Slf4j
public class RedisBaseConfig {
@Value("${auth.redis.host}")
protected String host;
@Value("${auth.redis.password}")
protected String password;
@Value("${auth.redis.database}")
protected Integer database;
protected static final int timeout = 3200;
protected static final int PORT = 6379;
@Bean(name = "authJedisConnectionFactory")
public JedisConnectionFactory authJedisConnectionFactory() {
RedisStandaloneConfiguration factory = new RedisStandaloneConfiguration();
factory.setDatabase(database);
factory.setHostName(host);
factory.setPassword(password);
factory.setPort(PORT);
log.info("redis config=========" + host);
JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder();
JedisClientConfiguration jedisClientConfiguration = jedisClientConfigurationBuilder.usePooling().poolConfig(poolConfig()).build();
return new JedisConnectionFactory(factory,jedisClientConfiguration);
}
private JedisPoolConfig poolConfig() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(1000);
poolConfig.setMinIdle(100);
poolConfig.setMaxWaitMillis(1000 * 60 * 3);
//11.11新增配置,解决空闲时期的连接释放
poolConfig.setTestWhileIdle(true);
poolConfig.setMinEvictableIdleTimeMillis(30 * 1000);
poolConfig.setTimeBetweenEvictionRunsMillis(60 * 1000);
poolConfig.setNumTestsPerEvictionRun(-1);
poolConfig.setTestOnReturn(true);
//
return poolConfig;
}
/**
* 初始化Redis连接池
*/
// @Bean
// public JedisPool generateJedisPoolFactory() {
// JedisPoolConfig poolConfig = new JedisPoolConfig();
// poolConfig.setMaxTotal(210);
// poolConfig.setMaxIdle(20);
// poolConfig.setMinIdle(5);
// poolConfig.setMaxWaitMillis(1000 * 60 * 3);
// // 连接耗尽时是否阻塞, false报异常,true阻塞直到超时, 默认true
// poolConfig.setBlockWhenExhausted(Boolean.TRUE);
JedisPool jedisPool = new JedisPool(poolConfig, host, port, timeout);
// // 若设置了Redis密码,请调用如下构造函数
// JedisPool jedisPool = new JedisPool(poolConfig, host, PORT, timeout, password);
// return jedisPool;
// }
}
先用这个配置在测试环境跑一下,要是还报错的话,就打算不用jedis了。
后续:还是报Socket closed,后来仔细阅读了下代码,发现之前写的redis工具类里是这样引用的
每次使用的时候都是用的类下的变量jedis去连接池里获取一个实力,但是这个类是个单例,大家都公用这个一个变量,也就是说不同线程都用的一个变量,有线程把他close,那么正在用的是不是就报socket close了。然后我把这个类的变量改到了每个方法里
目前在测试环境跑了一段时间没有再发现Socket closed的报错了