Spring Redis開啓事務支持錯誤用法致使服務不可用

 

 

1.事故背景html

在APP訪問服務器接口時須要從redis中獲取token進行校驗,服務器上線後發現一開始能夠正常訪問,但只要短期內請求量增加服務則沒法響應mysql

2.排查流程redis

(1)使用top指令查看CPU資源佔用還遠遠達不到瓶頸,排查由於CPU資源不足致使服務不可用的可能spring

(2)查看tomcat線程池配置,默認最大線程數爲200,理論上能夠支持目前服務器的訪問量sql

(3)使用jmap指令保存堆棧信息,jmap -dump:format=b,file=dump.log pid,pid爲進程號tomcat

(4)使用Java visualVM打開保存的堆棧日誌dump.log,發現絕大部分的線程都阻塞在從redis鏈接池中獲取鏈接的代碼,以下圖所示服務器

 3.原理分析spa

 根據堆棧日誌所顯示得知線程訪問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

相關文章
相關標籤/搜索