redis由于它是一种基于内存操作的高性能分布式数据库,很受大众喜爱,经常出现项目中,可以说是开发必备技能。
问题在开发过程中使用不当会造成redis的各种异常:
- 第一种经常出现读超时:
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
- 第二种获取不到连接
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the poolspringboot默认初始化的连接池和timeout时间
主要看RedisAutoConfiguration类和JedisConnectionFactory类。
如果我们引入starter,基于springboot自动注入原理,类RedisAutoConfiguration会被加载到spring容器中,RedisAutoConfiguration部分代码:
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}
protected final JedisConnectionFactory applyProperties(
JedisConnectionFactory factory) {
configureConnection(factory);
if (this.properties.isSsl()) {
factory.setUseSsl(true);
}
factory.setDatabase(this.properties.getDatabase());
if (this.properties.getTimeout() > 0) {
factory.setTimeout(this.properties.getTimeout());
}
return factory;
}
省略部分代码
看到会自动注入JedisConnectionFactory这个bean到容器中
public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
private final static Log log = LogFactory.getLog(JedisConnectionFactory.class);
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
JedisConverters.exceptionConverter());
private static final Method SET_TIMEOUT_METHOD;
private static final Method GET_TIMEOUT_METHOD;
static {
// We need to configure Jedis socket timeout via reflection since the method-name was changed between releases.
Method setTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class, "setTimeout", int.class);
if (setTimeoutMethodCandidate == null) {
// Jedis V 2.7.x changed the setTimeout method to setSoTimeout
setTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class, "setSoTimeout", int.class);
}
SET_TIMEOUT_METHOD = setTimeoutMethodCandidate;
Method getTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class, "getTimeout");
if (getTimeoutMethodCandidate == null) {
getTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class, "getSoTimeout");
}
GET_TIMEOUT_METHOD = getTimeoutMethodCandidate;
}
private JedisShardInfo shardInfo;
private String hostName = "localhost";
private int port = Protocol.DEFAULT_PORT;
private int timeout = Protocol.DEFAULT_TIMEOUT;
private String password;
private boolean usePool = true;
private boolean useSsl = false;
private Pool pool;
private JedisPoolConfig poolConfig = new JedisPoolConfig();
private int dbIndex = 0;
private String clientName;
private boolean convertPipelineAndTxResults = true;
private RedisSentinelConfiguration sentinelConfig;
private RedisClusterConfiguration clusterConfig;
private JedisCluster cluster;
private ClusterCommandExecutor clusterCommandExecutor;
其中的Protocol.DEFAULT_TIMEOUT 默认是2000,单位是毫秒,即读超时时间是默认2秒钟。
再看JedisPoolConfig
public class JedisPoolConfig extends GenericObjectPoolConfig {
public JedisPoolConfig() {
// defaults to make your life with connection pool easier :)
setTestWhileIdle(true);
setMinEvictableIdleTimeMillis(60000);
setTimeBetweenEvictionRunsMillis(30000);
setNumTestsPerEvictionRun(-1);
}
}
public class GenericObjectPoolConfig extends baseObjectPoolConfig {
public static final int DEFAULT_MAX_TOTAL = 8;
public static final int DEFAULT_MAX_IDLE = 8;
可以看到默认连接最大是8个,空闲最大也是8个。
异常场景重现- 读取超时
byte[] n = "abcdef".getBytes();
for (int i = 0; i< 100; i++) {
new Thread(() -> {
jedisOperate.incr(n);
try {
// 模拟耗时操作
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@Component
public class JedisOperate {
private RedisTemplate redisTemplate;
private Jedis jedis;
JedisOperate(RedisTemplate redisTemplate, CommonProperties properties) {
this.redisTemplate = redisTemplate;
}
@PostConstruct
public void init() {
JedisConnection connection = (JedisConnection) redisTemplate.getConnectionFactory().getConnection();
this.jedis = connection.getNativeConnection();
}
public Long incr(byte[] key) {
Long incr = jedis.incr(key);
return incr;
}
public boolean exists(byte[] key) {
return jedis.exists(key);
}
}
以上会抛出异常:
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out at redis.clients.jedis.Connection.connect(Connection.java:207) at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:93) at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1767) at redis.clients.jedis.JedisFactory.makeObject(JedisFactory.java:106) at org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:888) at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:432) at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:361) at redis.clients.util.Pool.getResource(Pool.java:49)
- 获取不到连接
代码做一下修改
byte[] n = "abcdef".getBytes();
for (int i = 0; i< 100; i++) {
new Thread(() -> {
// 不用sleep时间
jedisOperate.incr(n);
}).start();
}
@Component
public class JedisOperate {
private RedisTemplate redisTemplate;
JedisOperate(RedisTemplate redisTemplate, CommonProperties properties) {
this.redisTemplate = redisTemplate;
}
@PostConstruct
public void init() {
// JedisConnection connection = (JedisConnection) redisTemplate.getConnectionFactory().getConnection();
// this.jedis = connection.getNativeConnection();
}
public Long incr(byte[] key) {
Jedis jedis = ((JedisConnection) redisTemplate.getConnectionFactory().getConnection()).getNativeConnection();
Long incr = jedis.incr(key);
return incr;
}
}
两次区别在于第一种是共用一个jedis,第二种是每次都去获取。
抛出异常:
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool at redis.clients.util.Pool.getResource(Pool.java:53) at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226) at redis.clients.jedis.JedisPool.getResource(JedisPool.java:16) at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:194)
报错原因是因为每次都去获取连接,但是用完又没有close掉,这个也是我们容易忽略的地方,要特别注意。如果使用RedisTemplate组件,spring最终会帮我们调用close方法的。
修改最大连接和超时时间直接在项目的 application.properties文件或application.yml文件增加以下配置:
以application.yml为例
spring:
redis:
host: 127.0.0.1
port: 6379
pool:
max-active: 16
timeout: 5000
最大连接=16,超时时间5秒。



