乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

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

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

      圖片


      一、前言

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

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

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

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

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

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

      二、排查

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

      2.1 推測(cè)一

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

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

      2.2 推測(cè)二

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

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

      圖片

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

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

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

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

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

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

      2.3 驗(yàn)證推測(cè)二

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

      圖片

      為了驗(yàn)證上面的推論,我寫了一個(gè) Demo 程序。

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

      圖片

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

      圖片

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

      圖片

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

      圖片

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

      圖片

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

      2.4 推測(cè)三

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

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

      圖片

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

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

      圖片

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

      2.5 驗(yàn)證推測(cè)三

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

      圖片

      為了驗(yàn)證上面的場(chǎng)景,我們來做個(gè)實(shí)驗(yàn):

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

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

      圖片

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

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

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

      圖片

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

      圖片

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

      圖片圖片

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

      圖片

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

      圖片

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

      圖片

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

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

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

      三、源碼解析

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

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

      圖片

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

      圖片

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

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

      圖片

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

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

      比如下面的的遞增命令并不會(huì)返回遞增后的結(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)建兩個(gè) StringRedisTemplate,一個(gè)專門用來執(zhí)行 Redis 事務(wù),一個(gè)用來執(zhí)行普通的 Redis 命令。

      4.1 方案一

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

      圖片

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

      圖片

      4.2 方案二

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

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

      代碼如下所示:

      圖片

      接下來在測(cè)試的 Service 類中注入兩個(gè)不同的 StringRedisTemplate 實(shí)例,代碼如下所示:

      圖片

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

      圖片

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

      圖片

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

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


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

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多