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

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

文章插圖
下面是針對這段代碼的解釋,簡單來說就是開啟事務,將 Redis 命令順序放到一個隊列中,然后最后一起執行,且保證原子性 。
setEnableTransactionSupport表示是否開啟事務支持,默認不開啟 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
難道開啟了 Redis 事務,還能影響 Spring 事務中的 Redis 操作?
2.5 驗證推測三如下表,序號 3 和 序號 4 的場景都是開啟了 Redis 的事務支持,兩個場景的區別是是否加了 @Transactional 注解 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
為了驗證上面的場景,我們來做個實驗:
  • 先開啟 Redis 事務支持,然后執行 Redis 的事務命令 multi和 exec。
  • 驗證場景 3:在 @Transactional 注解的方法中執行 Redis 的遞增操作 。
  • 驗證場景 4:在非 @Transactional 注解的方法中執行 Redis 的遞增操作
2.5.1 執行 Redis 事務首先就用 Redis 的 multi 和 exec 命令來設置兩個 key 的值 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
如下圖所示,設置成功了 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
2.5.2 @Transactional 中執行 Redis 命令接下來在標注有 @Transactional 注解的方法中執行 Redis 的遞增操作 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
多次執行這個命令返回的結果都是 null,這不就正好重現了!
一次 Redis 事務使用不當引發的生產事故

文章插圖
再來看 Redis 中 count 的值,發現每執行一次 API 請求調用,都會遞增 1,所以雖然命令返回的是 null,但最后 Redis 中存放的還是遞增后的結果 。
一次 Redis 事務使用不當引發的生產事故

文章插圖

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

文章插圖
接下來我們驗證下場景 4,先執行 Redis 事務操作,然后在不添加 @Transactional 注解的方法中執行 Redis 遞增操作 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
用 Postman 調用這個接口后 , 正常返回自增后的結果,并不是返回 null 。說明在非 @Transactional 中執行 Redis 操作并沒有受到 Redis 事務的影響 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
四個場景的結論如下所示,只有第三個場景下,Redis 的遞增操作才會返回 null 。
一次 Redis 事務使用不當引發的生產事故

文章插圖
問題原因找到了,說明 RedisTemplete 開啟了 Redis 事務支持后,在 @Transactional 中執行的 Redis 命令也會被認為是在 Redis 事務中執行的 , 要執行的遞增命令會被放到隊列中,不會立即返回執行后的結果,返回的是一個 null,需要等待事務提交時,隊列中的命令才會順序執行,最后 Redis 數據庫的鍵值才會遞增 。
三、源碼解析那我們就看下為什么開啟了 Redis 事務支持,效果就不一樣了 。
找到 Redis 執行命令的核心方法,execute 方法 。
一次 Redis 事務使用不當引發的生產事故

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

文章插圖
接著往下看,關鍵代碼如下所示,當開啟了 Redis 事務支持 , 且添加了 @Transactional 注解時,就會執行 Redis 的 mutil 命令 。
關鍵代碼:conn.multi();
一次 Redis 事務使用不當引發的生產事故

文章插圖
Redis Multi 命令用于標記一個事務塊的開始,事務塊內的多條命令會按照先后順序被放進一個隊列當中,最后由 EXEC 命令原子性(atomic)地執行 。
真相大白,開啟 Redis 事務支持 + @Transactional 注解后,最后其實是標記了一個 Redis 事務塊,后續的操作命令是在這個事務塊中執行的 。
比如下面的的遞增命令并不會返回遞增后的結果,而是返回 null 。

推薦閱讀