待解決的問題
Spring session(redis存儲方式)監聽導緻建立大量redisMessageListenerContailner-X線程
解決辦法
為spring session添加springSessionRedisTaskExecutor線程池。
/**
* 用于spring session,防止每次建立一個線程
* @return
*/
@Bean
public ThreadPoolTaskExecutor springSessionRedisTaskExecutor(){
ThreadPoolTaskExecutor springSessionRedisTaskExecutor = new ThreadPoolTaskExecutor();
springSessionRedisTaskExecutor.setCorePoolSize(8);
springSessionRedisTaskExecutor.setMaxPoolSize(16);
springSessionRedisTaskExecutor.setKeepAliveSeconds(10);
springSessionRedisTaskExecutor.setQueueCapacity(1000);
springSessionRedisTaskExecutor.setThreadNamePrefix("Spring session redis executor thread: ");
return springSessionRedisTaskExecutor;
}
原因
在Spring Session(redis)的配置類源碼中(RedisHttpSessionConfiguration):
@Autowired(
required = false //該處理監聽的線程池不是必須的,如果不自定義預設将使用SimpleAsyncTaskExecutor線程池
)
@Qualifier("springSessionRedisTaskExecutor")
public void setRedisTaskExecutor(Executor redisTaskExecutor) {
this.redisTaskExecutor = redisTaskExecutor;
}
springSessionRedisTaskExecutor不是必須的,如果不自定義則spring預設将使用SimpleAsyncTaskExecutor線程池。
題外話
SimpleAsyncTaskExecutor:每次都将建立新的線程(說是“線程池”,其實并非真正的池化,但它可以設定最大并發線程數量。)
@EnableAsync開啟異步方法,背後預設使用的就是這個線程池。使用異步方法時如果業務場景存在頻繁的調用(該異步方法),請自定義線程池,以防止頻繁建立線程導緻的性能消耗。如果該異步方法存在阻塞的情況,又調用量大,注意有可能導緻OOM(線程還未結束,又增加了更多的線程,最後導緻記憶體溢出)。@Async注解可以選擇使用自定義線程池。
它建立了SimpleAsyncTaskExecutor
說回RedisHttpSessionConfiguration,我們接着看:
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(this.redisConnectionFactory);
if (this.redisTaskExecutor != null) {
container.setTaskExecutor(this.redisTaskExecutor);
}
if (this.redisSubscriptionExecutor != null) {
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
}
container.addMessageListener(this.sessionRepository(), Arrays.asList(new PatternTopic("__keyevent@*:del"), new PatternTopic("__keyevent@*:expired")));
container.addMessageListener(this.sessionRepository(), Collections.singletonList(new PatternTopic(this.sessionRepository().getSessionCreatedChannelPrefix() + "*")));
return container;
}
RedisMessageListenerContainer正是處理監聽的類,RedisMessageListenerContainer設定了不為空的redisTaskExecutor,因為spring session預設沒有配置該Executor,那RedisMessageListenerContainer在處理監聽時怎麼使用線程呢?我們接着看RedisMessageListenerContainer的源碼:
public void afterPropertiesSet() {
if (this.taskExecutor == null) {
this.manageExecutor = true;
this.taskExecutor = this.createDefaultTaskExecutor();
}
if (this.subscriptionExecutor == null) {
this.subscriptionExecutor = this.taskExecutor;
}
this.initialized = true;
}
protected TaskExecutor createDefaultTaskExecutor() {
String threadNamePrefix = this.beanName != null ? this.beanName + "-" : DEFAULT_THREAD_NAME_PREFIX;
return new SimpleAsyncTaskExecutor(threadNamePrefix);
}
afterPropertiesSet()這個方法熟悉吧,這個方法将在所有的屬性被初始化後調用(InitializingBean接口細節這裡不再贅述)。
是以如果使用者沒有定義springSessionRedisTaskExecutor,Spring session将調用createDefaultTaskExecutor()方法建立SimpleAsyncTaskExecutor線程池。而這個“線程池”處理任務時每次都建立新的線程。是以你會發現很多個redisMessageListenerContailner-X線程。