vlambda博客
学习文章列表

redis默认连接池的这个坑,你真的知道吗?

    前两天观察日志,发现了一个问题。redis连接超时。但是检测了一下线上redis的用量,并不是非常的多,使用slowlog命令记录了比较慢的指令,发现也并不是很多。当时的我,有点懵逼。


        后来仔细看了一下,有一行日志引起了我的注意,如下图所示:
 
   
   
 
RedisMessageListenerContainer类无法取消对订阅的订阅。


那么问题来了,何为:RedisMessageListenerContainer ?

经常使用redis的同学们应该对redis的发布订阅功能并不陌生,点开RedisMessageListenerContainer的类,发现类里有着作者写的注释。

 我勉为其难的用自己的工地英语尝试着翻译了一波,大概意思是说:这是为Redis消息监听器提供异步行为的容器。处理低层次的倾听细节,转换和消息发送。与低级别的Redis(每个订阅一个连接)相反,容器只使用一个连接。

    Soga~,原来是个做消息订阅的类。那么继续看redis配置类的代码。
  
    
    
  
@Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerCutVideoAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.addMessageListener(listenerCutVideoAdapter, new PatternTopic(channelName)); return container; }
等等,有一行代码引起了我们的注意:
  
    
    
  
container.setConnectionFactory(connectionFactory);

这个ConnectionFactory看上去像个连接工厂,会不会里面有点玄机呢?

点进去看看,发现果真是这样。


这个RedisConnectionFactory是用@Nllable标注的,代表该对象可以为空。类里有两个方法为createDefaultTaskExecutor(),和afterPropertiesSet();

afterPropertiesSet()大家应该都不陌生,这个方法将在所有的属性被初始化后调用(对于InitializingBean的细节这里不再赘述)。

  
    
    
  
public void afterPropertiesSet() { if (taskExecutor == null) { manageExecutor = true; taskExecutor = createDefaultTaskExecutor(); }
if (subscriptionExecutor == null) { subscriptionExecutor = taskExecutor; }
initialized = true; }
而在没有显式的指定线程池的情况下,createDefaultTaskExecutor会创建一个默认的线程池,名字叫SimpleAsyncTaskExecutor。
而这个“线程池”处理任务时每次都创建新的线程。所以你会发现很多个redisMessageListenerContailner-X线程。

所以真相大白了,罪魁祸首就是这个RedisMessageListenerContainer监听使用了默认的线程池,导致创建大量redisMessageListenerContailner-X线程,达到了配置里定义的最大线程数量了。


最后的题外话:
Spring 已经实现的异常线程池:

1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
3.ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
4.SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类
5. ThreadPoolTaskExecutor :最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装