成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

數據庫 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,如下所示:

圖片

官方注釋已經說明什么情況下會返回 null:

  • 當在 pipeline(管道)中使用這個 increment 方法時會返回 null。
  • 當在 transaction(事務)中使用這個 increment 方法時會返回 null。

事務提供了一種將多個命令打包,然后一次性、有序地執行機制.

多個命令會被入列到事務隊列中,然后按先進先出(FIFO)的順序執行。

事務在執行過程中不會被中斷,當事務隊列中的所有命令都被執行完畢之后,事務才會結束。(內容來自 Redis 設計與實現)

繼續看代碼,發現在操作 Redis 的 ServiceImpl 實現類的上面添加了一個 @Transactional 注解,推測是不是這個注解影響了 Redis 的操作結果。

2.3 驗證推測二

如下面的表格所示,第二行中沒有添加 Spring 的事務注解 @Transactional?時,執行 Redis 的遞增命令肯定是正常的,而接下來要驗證的是表格中的第一行:加了 @Transactional 是否對 Redis 的命令有影響。

圖片

為了驗證上面的推論,我寫了一個 Demo 程序。

Controller 類,定義了一個 API,用來模擬前端發起的請求:

圖片

Service 實現類,定義了一個方法,用來遞增 Redis 中的 count 鍵,每次遞增 1,然后返回命令執行后的結果。而且這個 Service 方法加了@Transactional 注解。

圖片

Postman 測試下,發現每發一次請求,count 都會遞增 1,并沒有返回 null。

圖片

然后到 Redis 中查看數據,count 的值也是遞增后的值 38,也不是 null。

圖片

通過這個實驗說明在 @Transactional 注解的方法里面執行 Redis 的操作并不會返回 null,結論我記錄到了表格中。

圖片

所以說上面的推論不成立(加了 @Transactional 注解并不影響),到這里線索似乎斷了。

2.4 推測三

然后跟當時做這塊功能的開發人員說明了情況,告訴他可能是 Redis 事務造成的,然后問有沒有其他同學在凌晨執行過 Redis 事務相關的 Job。

他說最近有同事加過 Redis 的事務功能,在凌晨執行 Job 的時候用到事務。我將這位同事加的代碼簡化后如下所示:

圖片

下面是針對這段代碼的解釋,簡單來說就是開啟事務,將 Redis 命令順序放到一個隊列中,然后最后一起執行,且保證原子性。

setEnableTransactionSupport表示是否開啟事務支持,默認不開啟。

圖片

難道開啟了 Redis 事務,還能影響 Spring 事務中的 Redis 操作?

2.5 驗證推測三

如下表,序號 3 和 序號 4 的場景都是開啟了 Redis 的事務支持,兩個場景的區別是是否加了 @Transactional 注解。

圖片

為了驗證上面的場景,我們來做個實驗:

  • 先開啟 Redis 事務支持,然后執行 Redis 的事務命令 multi  和 exec 。
  • 驗證場景 3:在 @Transactional 注解的方法中執行 Redis 的遞增操作。
  • 驗證場景 4:在非 @Transactional 注解的方法中執行 Redis 的遞增操作

2.5.1 執行 Redis 事務

首先就用 Redis 的 multi 和 exec 命令來設置兩個 key 的值。

圖片

如下圖所示,設置成功了。

圖片

2.5.2 @Transactional 中執行 Redis 命令

接下來在標注有 @Transactional 注解的方法中執行 Redis 的遞增操作。

圖片

多次執行這個命令返回的結果都是 null,這不就正好重現了!

圖片

再來看 Redis 中 count 的值,發現每執行一次 API 請求調用,都會遞增 1,所以雖然命令返回的是 null,但最后 Redis 中存放的還是遞增后的結果。

圖片

圖片

接下來我們驗證下場景 4,先執行 Redis 事務操作,然后在不添加 @Transactional 注解的方法中執行 Redis 遞增操作。

圖片

用 Postman 調用這個接口后,正常返回自增后的結果,并不是返回 null。說明在非 @Transactional 中執行 Redis 操作并沒有受到 Redis 事務的影響。

圖片

四個場景的結論如下所示,只有第三個場景下,Redis 的遞增操作才會返回 null。

圖片

問題原因找到了,說明 RedisTemplete 開啟了 Redis 事務支持后,在 @Transactional 中執行的 Redis 命令也會被認為是在 Redis 事務中執行的,要執行的遞增命令會被放到隊列中,不會立即返回執行后的結果,返回的是一個 null,需要等待事務提交時,隊列中的命令才會順序執行,最后 Redis 數據庫的鍵值才會遞增。

三、源碼解析

那我們就看下為什么開啟了 Redis 事務支持,效果就不一樣了。

找到 Redis 執行命令的核心方法, execute 方法。

圖片

然后一步一步點進去看,關鍵代碼就是 211 行到 216 行,有一個邏輯判斷,當開啟了 Redis 事務支持后,就會去綁定一個連接(bindConnection?),否則就去獲取新的 Redis 連接(getConnection?)。這里我們是開啟了的,所以再到 bindConnection方法中查看如何綁定連接的。

圖片

接著往下看,關鍵代碼如下所示,當開啟了 Redis 事務支持,且添加了 @Transactional 注解時,就會執行 Redis 的 mutil 命令。

關鍵代碼:conn.multi();

圖片

Redis Multi 命令用于標記一個事務塊的開始,事務塊內的多條命令會按照先后順序被放進一個隊列當中,最后由 EXEC 命令原子性(atomic)地執行。

真相大白,開啟 Redis 事務支持 + @Transactional 注解后,最后其實是標記了一個 Redis 事務塊,后續的操作命令是在這個事務塊中執行的。

比如下面的的遞增命令并不會返回遞增后的結果,而是返回 null。

stringRedisTemplate.opsForValue().increment("count", 1);

而我們的生產環境重啟服務后,開啟的 Redis 事務支持又被重置為默認值了,所以后續的 Redis 遞增操作都能正常執行。

四、修復方案

目前想到了兩種解決方案:

方案一:每次 Redis 的事務操作完成后,關閉 Redis 事務支持,然后再執行 @Transactional 中的 Redis 命令。(有弊端)

方案二:創建兩個 StringRedisTemplate,一個專門用來執行 Redis 事務,一個用來執行普通的 Redis 命令。

4.1 方案一

方案一的寫法如下,先開啟事務支持,事務執行之后,再關閉事務支持。

圖片

但是這種寫法有個弊端,如果在執行 Redis 事務期間,在 @Transactional 注解的方法里面執行 Redis 命令,則還是會造成返回結果為 null。

圖片

4.2 方案二

弄兩個 RedisTemplate Bean,一個是用來執行 Redis 事務的,一個是用來執行普通 Redis 命令的(不支持事務)。不同的地方引入不同的 Bean 就可以了。

先創建一個 RedisConfig 文件,自動裝配兩個 Bean。一個 Bean 名為 stringRedisTemplate? 代表不支持事務的,執行命令后立即返回實際的執行結果。另外一個 Bean 名為 stringRedisTemplateTransaction,代表開啟 Redis 事務支持的。

代碼如下所示:

圖片

接下來在測試的 Service 類中注入兩個不同的 StringRedisTemplate 實例,代碼如下所示:

圖片

Redis 事務的操作改寫成這樣,且不需要手動開啟 Redis 事務支持了。用到的 StringRedisTemplate 是支持事務的那個實例。

圖片

在 Spring 的 @Tranactional 中執行的 Redis 命令如下所示,用到的 StringRedisTemplate 是不支持事務的那個實例。

圖片

然后還是按照上面場景 3 的測試步驟,先執行 testRedisMutil 方法,再執行 testTransactionAnnotations 方法。

驗證結果:Redis 遞增操作正常返回 count 的值,修復完成。

另外關于 Redis 事務使用還有一個坑,就是 Redis 連接未釋放,導致獲取不到連接了,這是下一個話題了~

參考資料:https://blog.csdn.net/qq_34021712/article/details/79606551

責任編輯:武曉燕 來源: 悟空聊架構
相關推薦

2024-06-28 10:01:04

2020-11-16 12:35:25

線程池Java代碼

2019-10-10 15:40:17

redisbug數據庫

2021-06-10 06:59:34

Redis應用API

2024-09-05 08:07:55

2021-05-20 10:02:50

系統Redis技巧

2021-09-11 19:00:54

Intro元素MemoryCache

2020-10-22 07:09:19

TCP網絡協議

2019-01-16 09:20:42

架構設計JVM FullGC宕機事故

2009-12-17 14:53:52

VS2008程序

2021-08-26 14:26:25

Java代碼集合

2024-02-04 08:26:38

線程池參數內存

2017-11-09 09:06:29

流量暴增優化

2021-07-11 09:34:45

ArrayListLinkedList

2022-11-16 08:00:00

雪花算法原理

2022-06-21 11:24:05

多線程運維

2020-12-09 08:59:59

MongoDB復合索事故

2021-11-01 17:29:02

Windows系統Fork

2011-08-18 13:49:32

筆記本技巧

2020-09-25 07:57:42

生產事故系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美日韩国产一区二区三区 | 国产日韩欧美 | 日本不卡一区二区三区在线观看 | 在线日韩 | 日韩久久久久久久 | 男人电影天堂 | 成人精品一区亚洲午夜久久久 | 不卡一区二区三区四区 | 久久精品视频网站 | 国产精品不卡视频 | 精品久久精品 | 91精品久久久久 | 国产精品久久久久久久久久久免费看 | 伊人狠狠| 免费在线视频精品 | 福利视频日韩 | 羞羞视频网站免费观看 | 免费同性女女aaa免费网站 | 中文字幕日韩欧美 | 久久精品黄色 | 欧美精品91 | 日本高清在线一区 | 日韩av在线中文字幕 | 91精品国产乱码久久久 | 中文字幕一区二区在线观看 | 精品视频在线播放 | 亚洲97 | 国产在线观看免费 | 韩日精品视频 | www.狠狠操 | 自拍偷拍欧美 | 日韩精品成人 | 国产精品一区二区三区在线 | 国产欧美一区二区三区国产幕精品 | 一区二区在线 | 97碰碰碰 | 久久亚洲国产 | 在线国产一区二区 | 国产日韩精品视频 | 精品国产欧美一区二区三区成人 | 亚洲一区二区欧美 |