久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

當(dāng) Redis 碰上 @Transactional,有大坑,,要注意,!

 goldbomb 2024-01-17 發(fā)布于四川
來源:悟空聊架構(gòu)

圖片


一、前言

最近項目的生產(chǎn)環(huán)境遇到一個奇怪的問題:

現(xiàn)象 :每天早上客服人員在后臺創(chuàng)建客服事件時,,都會創(chuàng)建失敗 ,。當(dāng)我們重啟 這個微服務(wù)后,后臺就可以正常創(chuàng)建了客服事件了,。到第二天早上又會創(chuàng)建失敗,,又得重啟這個微服務(wù)才行。

初步排查 :創(chuàng)建一個客服事件時,,會用到 Redis 的遞增操作來生成一個唯一的分布式 ID 作為事件 id,。代碼如下所示:

return redisTemplate.opsForValue().increment("count"1);

而恰巧每天早上這個遞增操作都會返回 null,進(jìn)而導(dǎo)致后面的一系列邏輯出錯,,保存客服事件失敗,。當(dāng)重啟微服務(wù)后,這個遞增操作又正常了,。

那么排查的方向就是 Redis 的操作為什么會返回 null 了,,以及為什么重啟就又恢復(fù)正常了。

二,、排查

根據(jù)上面的信息,,我們先來看看 Redis 的自增操作在什么情況下會返回 null。

2.1 推測一

根據(jù)重啟后就恢復(fù)正常,,我們推測晚上執(zhí)行了大量的 job,,大量 Redis 連接未釋放,當(dāng)早上再來執(zhí)行 Redis 操作時,,執(zhí)行失敗,。重啟后,連接自動釋放了,。

但是其他有使用到 Redis 的業(yè)務(wù)功能又是正常的,,所以推測一的方向有問題,排除 ,。

2.2 推測二

可能是 Redis 事務(wù)造成的問題,。這個推測的依據(jù)是根據(jù)下面的代碼來排查的。

直接看 redisTemplate 遞增的方法 increment,,如下所示:

圖片

官方注釋已經(jīng)說明什么情況下會返回 null:

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

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

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

事務(wù)在執(zhí)行過程中不會被中斷,,當(dāng)事務(wù)隊列中的所有命令都被執(zhí)行完畢之后,,事務(wù)才會結(jié)束。(內(nèi)容來自 Redis 設(shè)計與實現(xiàn))

繼續(xù)看代碼,,發(fā)現(xiàn)在操作 Redis 的 ServiceImpl 實現(xiàn)類的上面添加了一個 @Transactional 注解,,推測是不是這個注解影響了 Redis 的操作結(jié)果。

2.3 驗證推測二

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

圖片

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

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

圖片

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

圖片

Postman 測試下,發(fā)現(xiàn)每發(fā)一次請求,,count 都會遞增 1,,并沒有返回 null。

圖片

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

圖片

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

圖片

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

2.4 推測三

然后跟當(dāng)時做這塊功能的開發(fā)人員說明了情況,告訴他可能是 Redis 事務(wù)造成的,,然后問有沒有其他同學(xué)在凌晨執(zhí)行過 Redis 事務(wù)相關(guān)的 Job,。

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

圖片

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

setEnableTransactionSupport表示是否開啟事務(wù)支持,默認(rèn)不開啟,。

圖片

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

2.5 驗證推測三

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

圖片

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

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

首先就用 Redis 的 multi 和 exec 命令來設(shè)置兩個 key 的值,。

圖片

如下圖所示,設(shè)置成功了,。

圖片
2.5.2 @Transactional 中執(zhí)行 Redis 命令

接下來在標(biāo)注有 @Transactional 注解的方法中執(zhí)行 Redis 的遞增操作,。

圖片

多次執(zhí)行這個命令返回的結(jié)果都是 null,這不就正好重現(xiàn)了,!

圖片

再來看 Redis 中 count 的值,,發(fā)現(xiàn)每執(zhí)行一次 API 請求調(diào)用,都會遞增 1,,所以雖然命令返回的是 null,,但最后 Redis 中存放的還是遞增后的結(jié)果。

圖片圖片

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

圖片

用 Postman 調(diào)用這個接口后,,正常返回自增后的結(jié)果,,并不是返回 null。說明在非 @Transactional 中執(zhí)行 Redis 操作并沒有受到 Redis 事務(wù)的影響,。

圖片

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

圖片

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

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,,支持 RBAC 動態(tài)權(quán)限,、多租戶、數(shù)據(jù)權(quán)限,、工作流,、三方登錄、支付,、短信,、商城等功能

  • 項目地址:https://github.com/YunaiV/yudao-cloud
  • 視頻教程:https://doc./video/

三、源碼解析

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

找到 Redis 執(zhí)行命令的核心方法, execute 方法,。

圖片

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

圖片

接著往下看,,關(guān)鍵代碼如下所示,,當(dāng)開啟了 Redis 事務(wù)支持,且添加了 @Transactional 注解時,,就會執(zhí)行 Redis 的 mutil 命令,。

關(guān)鍵代碼:conn.multi();

圖片

Redis Multi 命令 用于標(biāo)記一個事務(wù)塊的開始,事務(wù)塊內(nèi)的多條命令會按照先后順序被放進(jìn)一個隊列當(dāng)中,,最后由 EXEC 命令原子性(atomic)地執(zhí)行,。

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

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

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

而我們的生產(chǎn)環(huán)境重啟服務(wù)后,開啟的 Redis 事務(wù)支持又被重置為默認(rèn)值了,,所以后續(xù)的 Redis 遞增操作都能正常執(zhí)行。

四、修復(fù)方案

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

  • 方案一:每次 Redis 的事務(wù)操作完成后,,關(guān)閉 Redis 事務(wù)支持,,然后再執(zhí)行 @Transactional 中的 Redis 命令。(有弊端 )
  • 方案二:創(chuàng)建兩個 StringRedisTemplate,,一個專門用來執(zhí)行 Redis 事務(wù),,一個用來執(zhí)行普通的 Redis 命令。

4.1 方案一

方案一的寫法如下,,先開啟事務(wù)支持,,事務(wù)執(zhí)行之后,再關(guān)閉事務(wù)支持,。

圖片

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

圖片

4.2 方案二

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

先創(chuàng)建一個 RedisConfig 文件,,自動裝配兩個 Bean,。一個 Bean 名為 stringRedisTemplate 代表不支持事務(wù)的,執(zhí)行命令后立即返回實際的執(zhí)行結(jié)果,。另外一個 Bean 名為 stringRedisTemplateTransaction,,代表開啟 Redis 事務(wù)支持的。

代碼如下所示:

圖片

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

圖片

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

圖片

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

圖片

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

驗證結(jié)果 :Redis 遞增操作正常返回 count 的值,,修復(fù)完成,。


    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多