Redis操作无响应问题处理记录及总结

项目中某个功能使用了Redis做缓存,使用代码如下:

 Map<String, String> map = new HashMap<>(16);
 configItems.stream()
         .map(ProductionLineConfigItemDTO::getDeviceId)
         .filter(Objects::nonNull)
         .forEach(deviceId -> map.put(redisKey + deviceId, String.valueOf(lineId)));

 redisTemplate.opsForValue().multiSet(map);

系统启动完,前几次对这个功能进行调用都是正常的,但经过几次调用后,调用就卡在最后一行不返回了,最关键的是会一直卡住,只能重启系统才能恢复正常。
很诡异的问题,redisTemplate的这种调用应该不会有锁,而且对同一个Key的操作只有这个地方有。
那现在只能看为什么会一直卡住了,猜测是Redis的连接配置中未配置超时时间,需要配置相关参数,配置如下:

  @Bean
  @ConfigurationProperties(prefix = "spring.redis")
  public JedisPoolConfig getRedisConfig() {
      JedisPoolConfig config = new JedisPoolConfig();
      config.setMaxWaitMillis(18000);
      return config;
  }

配置完后重启,然后发现在系统运行一段时间后报了以下错误:

2019-09-18 11:24:14.941 c762f911 org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
2019-09-18 11:24:14.941 c762f911 	at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:204)
2019-09-18 11:24:14.941 c762f911 	at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:348)
2019-09-18 11:24:14.941 c762f911 	at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:129)
2019-09-18 11:24:14.941 c762f911 	at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:92)
2019-09-18 11:24:14.941 c762f911 	at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:79)
2019-09-18 11:24:14.942 c762f911 	at org.springframework.boot.actuate.health.RedisHealthIndicator.doHealthCheck(RedisHealthIndicator.java:52)
2019-09-18 11:24:14.942 c762f911 	at org.springframework.boot.actuate.health.AbstractHealthIndicator.health(AbstractHealthIndicator.java:43)
2019-09-18 11:24:14.942 c762f911 	at org.springframework.boot.actuate.health.CompositeHealthIndicator.health(CompositeHealthIndicator.java:68)
2019-09-18 11:24:14.942 c762f911 	at org.springframework.cloud.netflix.eureka.EurekaHealthCheckHandler.getHealthStatus(EurekaHealthCheckHandler.java:103)
2019-09-18 11:24:14.942 c762f911 	at org.springframework.cloud.netflix.eureka.EurekaHealthCheckHandler.getStatus(EurekaHealthCheckHandler.java:99)
2019-09-18 11:24:14.942 c762f911 	at com.netflix.discovery.DiscoveryClient.refreshInstanceInfo(DiscoveryClient.java:1362)
2019-09-18 11:24:14.942 c762f911 	at com.netflix.discovery.InstanceInfoReplicator.run(InstanceInfoReplicator.java:100)
2019-09-18 11:24:14.942 c762f911 	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
2019-09-18 11:24:14.942 c762f911 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
2019-09-18 11:24:14.942 c762f911 	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
2019-09-18 11:24:14.942 c762f911 	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
2019-09-18 11:24:14.943 c762f911 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
2019-09-18 11:24:14.943 c762f911 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
2019-09-18 11:24:14.943 c762f911 	at java.lang.Thread.run(Thread.java:748)
2019-09-18 11:24:14.943 c762f911 Caused by: redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
2019-09-18 11:24:14.943 c762f911 	at redis.clients.util.Pool.getResource(Pool.java:51)
2019-09-18 11:24:14.943 c762f911 	at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226)
2019-09-18 11:24:14.943 c762f911 	at redis.clients.jedis.JedisPool.getResource(JedisPool.java:16)
2019-09-18 11:24:14.943 c762f911 	at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:194)
2019-09-18 11:24:14.943 c762f911 	... 18 common frames omitted
2019-09-18 11:24:14.943 c762f911 Caused by: java.util.NoSuchElementException: Timeout waiting for idle object

很明显是Redis连接池连接资源被耗光了!
从网上搜,有些说使用的Redis的版本太老,需要升级,于是升级,但升级后问题照旧;有些说是Redis的连接池太小,需要调整大小,默认的连接池为8个,但在这个项目里面,做验证的时候对Redis的操作很少,肯定是不会有8个并发同时对Redis进行操作。那有没有可能是连接没释放?难道使用RedisTemplate还需要手动释放连接?找资料,说如果Redis启动了事务的话,如果不使用Spring的事务管理机制,需要手动释放连接,但我的项目里面是使用了Spring的事务管理的!
到这个地方已经没有什么头绪了,于是只好检查代码,在另外一段使用Redis的代码中发现有以下调用:

 cursor = redisTemplate.opsForHash().scan(keyPattern,
         ScanOptions.scanOptions().match("*").count(100).build());

没用过scan这个方法,于是习惯性的点进去看这个方法是做什么的:

	/**
	 * Use a {@link Cursor} to iterate over entries in hash at {@code key}. 
* Important: Call {@link Cursor#close()} when done to avoid resource leak. * * @param key must not be {@literal null}. * @param options * @return * @since 1.4 */
Cursor<Map.Entry<HK, HV>> scan(H key, ScanOptions options);

然后惊奇的发现,注释里面已经写的很清楚了,使用scan操作完成后需要调用close方法释放连接!
这就好办了,改代码,再验证,一切都正常了!

对这个问题总结如下

  • scan方法需要手动关闭,否则可能造成连接被耗光
  • redisTemplate如果启用事务,通过Spring的Transactional注解可以自动对事务进行管理,但如果是自己管理事务时,在操作完成后需要手动释放连接,否则也会造成连接耗光
  • 默认情况下,连接被耗光后,RedisTemplate的操作将会被卡住不会返回,因为默认的为-1,不会超时,因此最好是将这个参数设置成一个合适的值,否则都不知道操作到底出了什么问题。

你可能感兴趣的:(Spring)