在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Redis 遞增修復操作

jf_ro2CN3Fa ? 來源:芋道源碼 ? 2023-06-14 09:47 ? 次閱讀

一、前言

二、排查

三、源碼解析

四、修復方案

一、前言

最近項目的生產環境遇到一個奇怪的問題:

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

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

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

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

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

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://github.com/YunaiV/ruoyi-vue-pro

視頻教程:https://doc.iocoder.cn/video/

二、排查

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

2.1 推測一

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

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

2.2 推測二

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

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

7757a1ba-0a53-11ee-962d-dac502259ad0.png

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

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

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

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

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

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

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

2.3 驗證推測二

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

77660b60-0a53-11ee-962d-dac502259ad0.png

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

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

777f4a12-0a53-11ee-962d-dac502259ad0.png

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

779b3696-0a53-11ee-962d-dac502259ad0.png

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

77ac9562-0a53-11ee-962d-dac502259ad0.png

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

77d27232-0a53-11ee-962d-dac502259ad0.png

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

77d83820-0a53-11ee-962d-dac502259ad0.png

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

2.4 推測三

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

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

77f786e4-0a53-11ee-962d-dac502259ad0.png

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

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

782042f0-0a53-11ee-962d-dac502259ad0.png

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

2.5 驗證推測三

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

78389c42-0a53-11ee-962d-dac502259ad0.png

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

先開啟 Redis 事務支持,然后執行 Redis 的事務命令 multi 和 exec 。

驗證場景 3:在 @Transactional 注解的方法中執行 Redis 的遞增操作。

驗證場景 4:在非 @Transactional 注解的方法中執行 Redis 的遞增操作

2.5.1 執行 Redis 事務

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

785b039a-0a53-11ee-962d-dac502259ad0.png

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

7871127a-0a53-11ee-962d-dac502259ad0.png

2.5.2 @Transactional 中執行 Redis 命令

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

78a66524-0a53-11ee-962d-dac502259ad0.png

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

78d62e30-0a53-11ee-962d-dac502259ad0.png

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

78e95b5e-0a53-11ee-962d-dac502259ad0.png78f7d882-0a53-11ee-962d-dac502259ad0.png

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

790e7bc8-0a53-11ee-962d-dac502259ad0.png

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

7932b3e4-0a53-11ee-962d-dac502259ad0.png

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

795da496-0a53-11ee-962d-dac502259ad0.png

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

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://github.com/YunaiV/yudao-cloud

視頻教程:https://doc.iocoder.cn/video/

三、源碼解析

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

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

7972aeae-0a53-11ee-962d-dac502259ad0.png

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

79951e62-0a53-11ee-962d-dac502259ad0.png

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

關鍵代碼:conn.multi();

79b52cc0-0a53-11ee-962d-dac502259ad0.png

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

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

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

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

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

四、修復方案

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

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

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

4.1 方案一

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

7a0274bc-0a53-11ee-962d-dac502259ad0.png

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

7a2d12e4-0a53-11ee-962d-dac502259ad0.png

4.2 方案二

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

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

代碼如下所示:

7a427f76-0a53-11ee-962d-dac502259ad0.png

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

7a5d9518-0a53-11ee-962d-dac502259ad0.png

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

7a7a695e-0a53-11ee-962d-dac502259ad0.png

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

7ac18d98-0a53-11ee-962d-dac502259ad0.png

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

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

責任編輯:彭菁

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 源碼
    +關注

    關注

    8

    文章

    641

    瀏覽量

    29213
  • 小程序
    +關注

    關注

    1

    文章

    234

    瀏覽量

    12136
  • Redis
    +關注

    關注

    0

    文章

    375

    瀏覽量

    10877

原文標題:當 Redis 碰上 @Transactional,有大坑,要注意!

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    如何使用Rust連接Redis

    Rust操作RedisRedis依賴庫 在Rust中有很多Redis的客戶端庫可以選擇,這里我們選擇使用redis-rs庫。在Cargo
    的頭像 發表于 09-19 16:22 ?2374次閱讀

    Redis Stream應用案例

    摘要: Redis Stream Redis最新的大版本5.0已經RC1了,其中最重要的Feature莫過于Redis Stream了,關于Redis Stream的基本使用介紹和設計
    發表于 06-26 17:15

    laravel使用redis

    laravel操作redis筆記!
    發表于 09-24 09:40

    Redis的安裝和使用步驟

    Python操作Redis之安裝和使用(一)
    發表于 09-29 09:29

    labview讀寫操作REDIS

    本帖最后由 SevenLi8408 于 2022-9-15 08:07 編輯 分享一個好用的非關系型緩存數據庫的使用方法。REDIS桌面管理軟件https://github.com
    發表于 08-15 10:32

    電池修復的基礎操作

    電池修復的基礎操作 在這里介紹的電動車蓄/電池修復/之基礎操作步驟,就是指常規使用的修復方法,修復
    發表于 11-10 13:44 ?1054次閱讀

    python操作redis

    --有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,
    發表于 11-28 11:02 ?766次閱讀
    python<b class='flag-5'>操作</b><b class='flag-5'>redis</b>

    基于多線程環境下值的遞增操作--原子操作

    因此在多線程環境中對一個變量進行讀寫時,我們需要有一種方法能夠保證對一個值的遞增操作是原子操作——即不可打斷性,一個線程在執行原子操作時,其它線程必須等待它完成之后才能開始執行該原子
    的頭像 發表于 01-10 11:16 ?6178次閱讀
    基于多線程環境下值的<b class='flag-5'>遞增</b><b class='flag-5'>操作</b>--原子<b class='flag-5'>操作</b>

    Springboot+redis操作多種實現

    一、Jedis,Redisson,Lettuce三者的區別共同點:都提供了基于Redis操作的Java API,只是封裝程度,具體實現稍有不同。 不同點: 1.1、Jedis 是Redis的Java
    的頭像 發表于 09-22 10:48 ?1835次閱讀
    Springboot+<b class='flag-5'>redis</b><b class='flag-5'>操作</b>多種實現

    mysql_redis在MySQL中操作Redis?

    ./oschina_soft/gitee-mysql_redis.zip
    發表于 06-22 14:35 ?2次下載
    mysql_<b class='flag-5'>redis</b>在MySQL中<b class='flag-5'>操作</b><b class='flag-5'>Redis</b>?

    Redis數據同步解決方案—NineData

    NineData(https://www.ninedata.cloud/)在Redis的同步上,提供了穩定和高效的解決方案,并且性能上也領先其他同步工具,特別是在同步的動態限流、數據對比修復和限流
    的頭像 發表于 06-05 15:31 ?829次閱讀
    <b class='flag-5'>Redis</b>數據同步解決方案—NineData

    Redis是什么?簡述它的優缺點?

    Redis是什么?簡述它的優缺點? Redis本質上是一個Key-Value類型的內存數據庫,很像Memcached,整個數據庫加載在內存當中操作,定期通過異步操作把數據庫中的數據fl
    的頭像 發表于 10-09 10:37 ?821次閱讀

    redis的increment方法

    實現對存儲在數據庫中的特定鍵的遞增操作。在本文中,我們將詳細介紹Redis的 INCR 方法,包括其原理、使用方法以及一些常見的應用場景。 首先,我們來看看Redis的 INCR 方法
    的頭像 發表于 12-05 09:57 ?1244次閱讀

    redis的主要方法

    Redis是一種基于內存的開源鍵值對存儲系統,常用于緩存、消息中間件、數據庫等場景。作為一個高性能的NoSQL存儲解決方案,Redis提供了豐富的方法用于操作數據。本文將詳細介紹Redis
    的頭像 發表于 12-05 09:59 ?813次閱讀

    redis使用多線程處理操作命令

    Redis 是一個使用多線程處理操作命令的開源內存數據庫系統。它以其高性能、可擴展性和靈活性而聞名,通常被用作緩存、消息代理和數據存儲等各種應用場景。在本文中,我們將詳盡、詳實、細致地探
    的頭像 發表于 12-05 10:25 ?580次閱讀
    主站蜘蛛池模板: 欧美有码视频| 国产三级跑| 艹逼视频免费| 日韩porn| 国产高清色视频免费看的网址 | 人人草97| 亚洲午夜视频在线观看| 日一日操一操| 亚洲韩国日本欧美一区二区三区| 四虎影院国产精品| 欧美三页| 天天艹天天操| 日本在线视频二区| 欧美成人午夜精品一区二区| 天天天天做夜夜夜夜| 国产xxxx极品bbw视色| 中文一级黄色片| 黄色三级视频在线观看| 狠狠干天天射| 清朝荒淫牲艳史在线播放| 人人干97| 日本久操视频| 中文字幕一区在线观看视频| 日本色婷婷| 手机看片三级| 中文字幕777| 国产成人永久免费视频| 视频在线视频免费观看| 色香蕉在线| 手机在线看福利| 综合se| 艹逼免费视频| 亚洲三级成人| 久久综合影视| 色天天综合色天天看| 国产性做久久久久久| 国产美女动态免费视频| 国产gav成人免费播放视频| 欧美线人一区二区三区| 国模娜娜扒开嫩木耳| 99精品久久99久久久久久|