一次 Redis 事務使用不當引發的生產事故


一次 Redis 事務使用不當引發的生產事故

文章插圖
這是悟空的第 170 篇原創文章
官網:http://www.passjava.cn
你好,我是悟空 。
本文主要內容如下:
一次 Redis 事務使用不當引發的生產事故

文章插圖
一、前言最近項目的生產環境遇到一個奇怪的問題:
現象:每天早上客服人員在后臺創建客服事件時,都會創建失敗 。當我們重啟這個微服務后,后臺就可以正常創建了客服事件了 。到第二天早上又會創建失敗,又得重啟這個微服務才行 。
初步排查:創建一個客服事件時 , 會用到 Redis 的遞增操作來生成一個唯一的分布式 ID 作為事件 id 。代碼如下所示:
return redisTemplate.opsForValue().increment("count", 1);而恰巧每天早上這個遞增操作都會返回 null,進而導致后面的一系列邏輯出錯 , 保存客服事件失敗 。當重啟微服務后,這個遞增操作又正常了 。
那么排查的方向就是 Redis 的操作為什么會返回 null 了,以及為什么重啟就又恢復正常了 。
二、排查根據上面的信息,我們先來看看 Redis 的自增操作在什么情況下會返回 null 。
2.1 推測一根據重啟后就恢復正常,我們推測晚上執行了大量的 job,大量 Redis 連接未釋放,當早上再來執行 Redis 操作時,執行失敗 。重啟后,連接自動釋放了 。
但是其他有使用到 Redis 的業務功能又是正常的,所以推測一的方向有問題,排除 。
2.2 推測二可能是 Redis 事務造成的問題 。這個推測的依據是根據下面的代碼來排查的 。
直接看 redisTemplate 遞增的方法 increment,如下所示:
一次 Redis 事務使用不當引發的生產事故

文章插圖
官方注釋已經說明什么情況下會返回 null:
  • 當在 pipeline(管道)中使用這個 increment 方法時會返回 null 。
  • 當在 transaction(事務)中使用這個 increment 方法時會返回 null 。
事務提供了一種將多個命令打包,然后一次性、有序地執行機制.
多個命令會被入列到事務隊列中 , 然后按先進先出(FIFO)的順序執行 。
【一次 Redis 事務使用不當引發的生產事故】事務在執行過程中不會被中斷,當事務隊列中的所有命令都被執行完畢之后,事務才會結束 。(內容來自 Redis 設計與實現)
繼續看代碼,發現在操作 Redis 的 ServiceImpl 實現類的上面添加了一個 @Transactional 注解,推測是不是這個注解影響了 Redis 的操作結果 。
2.3 驗證推測二如下面的表格所示,第二行中沒有添加 Spring 的事務注解 @Transactional時 , 執行 Redis 的遞增命令肯定是正常的 , 而接下來要驗證的是表格中的第一行:加了 @Transactional 是否對 Redis 的命令有影響 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
為了驗證上面的推論,我寫了一個 Demo 程序 。
Controller 類,定義了一個 API,用來模擬前端發起的請求:
一次 Redis 事務使用不當引發的生產事故

文章插圖
Service 實現類,定義了一個方法,用來遞增 Redis 中的 count 鍵,每次遞增 1,然后返回命令執行后的結果 。而且這個 Service 方法加了@Transactional 注解 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
Postman 測試下,發現每發一次請求,count 都會遞增 1,并沒有返回 null 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
然后到 Redis 中查看數據,count 的值也是遞增后的值 38 , 也不是 null 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
通過這個實驗說明在 @Transactional 注解的方法里面執行 Redis 的操作并不會返回 null,結論我記錄到了表格中 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
所以說上面的推論不成立(加了 @Transactional 注解并不影響),到這里線索似乎斷了 。
2.4 推測三然后跟當時做這塊功能的開發人員說明了情況,告訴他可能是 Redis 事務造成的,然后問有沒有其他同學在凌晨執行過 Redis 事務相關的 Job 。
他說最近有同事加過 Redis 的事務功能,在凌晨執行 Job 的時候用到事務 。我將這位同事加的代碼簡化后如下所示:

推薦閱讀