Spring-data-redis+Jedis+Redis集羣導致的阻塞問題

一、現象

1、項目上使用三個隊列來緩存數據,發現其中有一個隊列有很多數據,但是消費卻非常慢

2、代碼使用redisTemplate.opsForList().rightPop 來獲取隊列裏面的數據,這個命令對應的Redis命令是bRPop

3、通過調用鏈查看,發現brpop命令執行的耗時非常長,最大響應時間達到2秒多

4、在Redis客戶端直接使用BRPOP course 30獲取數據,執行非常快,並沒有感覺到阻塞

 

二、分析過程

1、BRPOP

是列表的阻塞式(blocking)彈出原語,它是 RPOP 命令的阻塞版本,當給定列表內沒有任何元素可供彈出的時候,連接將被 BRPOP 命令阻塞,直到等待超時或發現可彈出元素爲止。

2、測試環境是否可復現

通過測試環境測試,問題依舊

3、通過JedisCluster進行測試

採用JedisCluster直連的方式,發現並沒有阻塞的跡象

4、源碼分析

4.1、DefaultListOperations.rightPop()

 

4.2、JedisClusterConnection.bRPop

4.3、ClusterCommandExecutor.executeMuliKeyCommand

在這裏我們看到了這個方法其實內部是異步執行的

4.4、executor的創建

4.5、ThreadPoolTaskExecutor.corePoolSize

大家發現了沒有,corePoolSize是寫死的1,並且在4.4、executor的創建的地方並沒有修改它

4.6、能改變corePoolSize的大小嗎?

臥槽,這有點出乎我的意料,居然沒找到任何地方可以修改它的值

4.7、到此就能解釋了爲什麼有一個隊列裏面有數據,卻消費的非常慢的原因了,其實是另外兩個隊列裏面沒數據導致的阻塞,單線程執行,另外兩個隊列就把這個有數據的隊列的阻塞住了。
三、影響範圍

1、ClusterCommandExecutor裏面使用executor的方法:executeCommandAsyncOnNodes、executeMuliKeyCommand

2、ClusterCommandExecutor.executeCommandAsyncOnNodes調用的地方

3、ClusterCommandExecutor.executeCommandOnAllNodes調用的地方

4、ClusterCommandExecutor.executeMuliKeyCommand調用的地方

紅色箭頭標識的地方,就是本次問題發生的地方,一頭冷汗,我覺得最危險的地方應該是del命令+blpop/brpop,如果這樣組合起來,都不知道爲什麼del命令刪不掉鍵了。

四、解決方案

1、試試高版本的spring-data-redis?

我們現在使用的是spring-data-redis-1.8.18.RELEASE.jar,本來以爲高版本的會修復這個問題,然並卵

2、使用Redisson替換Jedis問題解決