Spring Redis開啟事務支援錯誤用法導致服務不可用
1.事故背景
在APP訪問伺服器介面時需要從redis中獲取token進行校驗,伺服器上線後發現一開始可以正常訪問,但只要短時間內請求量增長服務則無法響應
2.排查流程
(1)使用top指令檢視CPU資源佔用還遠遠達不到瓶頸,排查因為CPU資源不足導致服務不可用的可能
(2)檢視tomcat執行緒池配置,預設最大執行緒數為200,理論上可以支援目前伺服器的訪問量
(3)使用jmap指令儲存堆疊資訊,jmap -dump:format=b,file=dump.log pid,pid為程序號
(4)使用Java visualVM開啟儲存的堆疊日誌dump.log,發現絕大部分的執行緒都阻塞在從redis連線池中獲取連線的程式碼,如下圖所示
3.原理分析
根據堆疊日誌所顯示得知執行緒訪問redis前需要從連線池佇列中推出一個連線,當連線池沒有連線時,則會阻塞等待,阻塞等待的時間可以自行設定MAA_WAIT引數,預設是-1表示不限時等待,目前專案使用預設配置,所以所有的執行緒都會一直阻塞在獲取連線的步驟,如果設定了最大等待時間,當超過最大等待時間會報出Could not get a resource from the pool的異常
(1)在spring配置檔案中的stringRedisTemplate物件配置引數中打開了事務支援,而redis的事務支援是用MUTI和EXEC指令來支援,以下事務例項截圖來自菜鳥教程 https://www.runoob.com/redis/redis-transactions.html
(2)如果要保證在事務能正常執行,那麼在一個方法中多次操作redis必須是同一條連線,這樣才能保證事務能正常執行,所以在stringRedisTemplate會將連線繫結在當前執行緒,當第二次訪問redis時直接從當前執行緒中獲取連線,繫結連線原始碼如下:
(3)按照流程,先繫結連線,最後在finally程式碼塊中釋放連線,看起來並沒有問題,但跳進去releaseConnection方法的程式碼發現連線需要在事務提交後才能釋放,也就是說service方法上必須使用@Transation註解修飾,但因為業務方法上少寫了@Transation註解導致連線將一直繫結第一次獲取他的執行緒上,當執行緒池的執行緒被獲取完之後,其他執行緒就會就如阻塞等待狀態,導致服務不可用
(4)如果加上@Transation註解,那麼方法執行完之後將會執行TransactionSynchronizationUtils.invokeAfterCompletion這個方法,mysql事務也是在這個方法執行commit操作,如下圖所示方法的第一個引數是List<TransactionSynchronization> synchronizations,代表可以有多個事務,redis,mysql等,都會此進行事務提交操作,這裡使用多型,根據物件的具體型別執行不同的方法,redis則執行redis的事務提交操作,mysql則執行mysql的事務提交操作
(5)以下為redis事務提交的程式碼,也跟我們上面提到的一樣,傳送exec指令提交事務
4.如何修改程式碼
(1)確認實際需求是否需要事務支援,如果需要則在對應方法上加上@Transaction註解
(2)如果不需要事務支援則將enableTransactionSupport設定為false
&n