分布式鎖之redis6

一、分布式鎖介紹

之前我們都是使用本地鎖(synchronize、lock等)來避免共享資源并發操作導致數據問題,這種是鎖在當前進程內

那么在集群部署下,對于多個節點,我們要使用分布式鎖來避免共享資源并發操作導致數據問題,雖然還是鎖,但是是多個進程共用的鎖標記,可以用Redis、Zookeeper、Mysql等都可以實現。

案例:優惠券領劵限制張數、商品庫存超賣。

我們設計分布式鎖應該要考慮的東西:

  • 排他性:在分布式應用集群中,同一個方法在同一時間只能被一臺機器上的一個線程執行。

  • 容錯性:分布式鎖一定能得到釋放,比如客戶端奔潰或者網絡中斷,可能會導致鎖一直不被釋放,從而導致死鎖,我們可以設置鎖的過期時間。

  • 滿足可重入、高性能、高可用(集群部署)。

  • 注意分布式鎖的開銷、鎖粒度。

二、分布式鎖的實現

實現分布式鎖可以用 Redis、Zookeeper、Mysql數據庫這幾種 , 性能最好的是Redis且是最容易理解。

分布式鎖離不開 key - value 設置,key 是鎖的唯一標識,一般按業務來決定命名,比如想要給一種優惠券活動加鎖,key 命名為 “coupon:id” 。value就可以使用固定值,比如設置成1。

基于redis實現分布式鎖:

(1)、加鎖 setnx key value:

setnx 的含義就是 set if not exists,有兩個參數 setnx(key, value),該方法是原子性操作,如果 key 不存在,則設置當前 key 成功,返回 1;如果當前 key 已經存在,則設置當前 key 失敗,返回 0

(2)、解鎖 del (key):

得到鎖的線程執行完任務,需要釋放鎖,以便其他線程可以進入,調用 del(key)。

(3)、配置鎖超時 expire (key,30s):

客戶端奔潰或者網絡中斷,資源將會永遠被鎖住,即死鎖,因此需要給key配置過期時間,以保證即使沒有被顯式釋放,這把鎖也要在一定時間后自動釋放。

綜合的偽代碼:

method(){String key = "coupon:id"
?if(setnx(key,1) == 1){expire(key,30,TimeUnit.MILLISECONDS)try {//做對應的業務邏輯//查詢用戶是否已經領券//如果沒有則扣減庫存//新增領劵記錄} finally {del(key)}}else{
?//睡眠100毫秒,然后自旋調用本方法method()}
}

三、?基于Redis實現分布式鎖的幾種坑

上面我們寫的偽代碼中有幾個坑,我們分別來分析一下。

1、多個命令之間不是原子性操作,如setnxexpire之間,如果setnx成功,但是expire失敗,且宕機了,則這個資源就是死鎖。

解決方法:使用原子命令來設置和配置過期時間 setnx / setex,在java里面是

redisTemplate.opsForValue().setIfAbsent("key","value",30,TimeUnit.MILLISECONDS)

成功了返回true,失敗了返回false。?

2、業務超時,存在其他線程勿刪,設置key30秒過期,假如線程A執行很慢超過30秒,則key就被釋放了,其他線程B就得到了鎖,這個時候線程A執行完成,而B還沒執行完成,結果就是線程A刪除了線程B加的鎖,所以我們的value不能單單只是1。

解決方法:可以在 del 釋放鎖之前做一個判斷,驗證當前的鎖是不是自己加的鎖, 那 value 應該是當前線程的標識或者uuid。

String key = "coupon:id"
String value = Thread.currentThread().getId()
?
if(setnx(key,value) == 1){expire(key,30,TimeUnit.MILLISECONDS)try {//做對應的業務邏輯} finally {//刪除鎖,判斷是否是當前線程加的if(get(key).equals(value)){//還存在時間間隔del(key)}}
}else{//睡眠100毫秒,然后自旋調用本方法
?
}

?3、進一步細化誤刪,當線程A獲取到正常值value時,返回帶代碼中判斷期間鎖過期了,線程B剛好重新設置了新值,線程A那邊有判斷value是自己的標識,然后調用del方法,結果就是刪除了新設置的線程B的值。

解決辦法:由于redis沒有相關的原子性api,所以采用 lua腳本+redis來實現多個命令的原子性。由于【判斷和刪除】是lua腳本執行,所以要么全成功,要么全失敗。

總結:核心是保證多個指令原子性,加鎖使用setnx setex 可以保證原子性,解鎖采用 lua腳本+redis來保證原子性。

【判斷和刪除】的lua腳本:

//獲取lock的值和傳遞的值一樣,調用刪除操作返回1,否則返回0
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
?
//Arrays.asList(lockKey)是key列表,uuid是參數
Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), uuid);

?四、原生分布式鎖的具體實現

@RestController
@RequestMapping("/api/v1/coupon")
public class CouponController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@GetMapping("add")public JsonData saveCoupon(@RequestParam (value = "coupon_id",required = true)int couponId){//防止其他線程誤刪String uuid = UUID.randomUUID().toString();String lockKey = "lock:coupon:" + couponId;lock(couponId,uuid,lockKey);return JsonData.buildSuccess();}private void lock(int couponId,String uuid,String lockKey){//lua腳本String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";Boolean nativeLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,uuid, Duration.ofSeconds(30));System.out.println(uuid+"加鎖狀態:"+nativeLock);if(nativeLock){//加鎖成功try{//TODO 做相關業務邏輯TimeUnit.SECONDS.sleep(10L);} catch (InterruptedException e) {} finally {//解鎖Long result = stringRedisTemplate.execute( new DefaultRedisScript<>(script,Long.class), Arrays.asList(lockKey),uuid);System.out.println("解鎖狀態:"+result);}}else {//自旋操作try {System.out.println("加鎖失敗,睡眠5秒 進行自旋");TimeUnit.MILLISECONDS.sleep(5000);} catch (InterruptedException e) { }//睡眠一會再嘗試獲取鎖lock(couponId,uuid,lockKey);}}}

運行結果:

d124ae03-5de6-4e25-82b8-fb0b30d7c7fc加鎖狀態:true
54041d23-ab3c-492e-977b-99c9b531534f加鎖狀態:false
加鎖失敗,睡眠5秒 進行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加鎖狀態:false
加鎖失敗,睡眠5秒 進行自旋
54041d23-ab3c-492e-977b-99c9b531534f加鎖狀態:false
加鎖失敗,睡眠5秒 進行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加鎖狀態:false
加鎖失敗,睡眠5秒 進行自旋
解鎖狀態:1
54041d23-ab3c-492e-977b-99c9b531534f加鎖狀態:true
51f16a96-45cd-476b-95ff-2ee6cc398e37加鎖狀態:false
加鎖失敗,睡眠5秒 進行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加鎖狀態:false
加鎖失敗,睡眠5秒 進行自旋
解鎖狀態:1
51f16a96-45cd-476b-95ff-2ee6cc398e37加鎖狀態:true
解鎖狀態:1

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/76599.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/76599.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/76599.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ubuntu中使用安卓模擬器

本文這里介紹 使用 android studio Emulator &#xff0c; 當然也有 Anbox (Lightweight)&#xff0c; Waydroid (Best for Full Android Experience), 首先確保自己安裝了 android studio &#xff1b; sudo apt update sudo apt install openjdk-11-jdk sudo snap install…

二語習得理論(Second Language Acquisition, SLA)如何學習英語

二語習得理論&#xff08;Second Language Acquisition, SLA&#xff09;是研究學習者如何在成人或青少年階段學習第二語言&#xff08;L2&#xff09;的理論框架。該理論主要關注語言習得過程中的認知、社會和文化因素&#xff0c;解釋了學習者如何從初學者逐漸變得流利并能夠…

WinDbg. From A to Z! 筆記(下)

原文鏈接: WinDbg. From A to Z! 文章目錄 使用WinDbg臨界區相關命令示例 -- 查看臨界區其他有用的命令 WinDbg中的偽寄存器自動偽寄存器 WinDbg中的表達式其他操作默認的表達式計算方式 WinDbg中的重命名調試器命令語言編程控制流命令程序執行 WinDbg 遠程調試事件監控WinDbg …

RainbowDash 的旅行

D RainbowDash 的旅行 - 第七屆校賽正式賽 —— 補題 題目大意&#xff1a; 湖中心有一座島&#xff0c;湖的外圍有 m m m 間木屋&#xff08;圍繞小島&#xff09; &#xff0c;第 i i i 間木屋和小島之間有 a i a_i ai? 座 A A A 類橋&#xff0c; b i b_i bi? 座 B …

MySQL-SQL-DDL語句、表結構創建語句

一.SQL SQL&#xff1a;一門操作關系型數據庫的編程語言&#xff0c;定義操作所有關系型數據庫的統一標準 二. DDL-數據庫 1. 查詢所有數據庫 命令&#xff1a;show databases; 2. 查詢當前數據庫 命令&#xff1a;select database(); 3. 創建數據庫 命令&#xff1a;create da…

Sora結構猜測

方案&#xff1a;VAE Encoder&#xff08;視頻壓縮&#xff09; -> Transform Diffusion &#xff08;從視頻數據中學習分布&#xff0c;并根據條件生成新視頻&#xff09; -> VAE Decoder &#xff08;視頻解壓縮&#xff09; 從博客出發&#xff0c;經過學術Survey&am…

TortoiseSVN設置忽略清單

1.TortoiseSVN > Properties&#xff08;如果安裝了 TortoiseSVN&#xff09;。 2. 在彈出的屬性窗口中&#xff0c;點擊 New > Other。 4. 在 Property name 中輸入 svn:ignore 。 5. 在 Property value 中輸入要忽略的文件夾或文件名稱&#xff0c;例如&#xff1a; #…

深入解析Java哈希表:從理論到實踐

哈希表&#xff08;Hash Table&#xff09;是計算機科學中最重要的數據結構之一&#xff0c;也是Java集合框架的核心組件。本文將以HashMap為切入點&#xff0c;深入剖析Java哈希表的實現原理、使用技巧和底層機制。 一、哈希表基礎原理 1. 核心概念 鍵值對存儲&#xff1a;通…

leetcode:1582. 二進制矩陣中的特殊位置(python3解法)

難度&#xff1a;簡單 給定一個 m x n 的二進制矩陣 mat&#xff0c;返回矩陣 mat 中特殊位置的數量。 如果位置 (i, j) 滿足 mat[i][j] 1 并且行 i 與列 j 中的所有其他元素都是 0&#xff08;行和列的下標從 0 開始計數&#xff09;&#xff0c;那么它被稱為 特殊 位置。 示…

《數字圖像處理》教材尋找合作者

Rafael Gonzalez和Richard Woods所著的《數字圖像處理》關于濾波器的部分幾乎全錯&#xff0c;完全從零開始寫&#xff0c;困難重重。關于他的問題已經描述在《數字圖像處理&#xff08;面向新工科的電工電子信息基礎課程系列教材&#xff09;》。 現尋找能夠共同討論、切磋、…

為 Jenkins Agent 添加污點(Taint)容忍度(Toleration)

在 Kubernetes&#xff08;k8s&#xff09;環境中使用 Jenkins 時&#xff0c;為 Jenkins Agent 添加污點&#xff08;Taint&#xff09;容忍度&#xff08;Toleration&#xff09;是一種常見的配置操作&#xff0c;它允許 Jenkins Agent Pod 被調度到帶有特定污點的節點上。下…

LeetCode算法題(Go語言實現)_28

題目 Dota2 的世界里有兩個陣營&#xff1a;Radiant&#xff08;天輝&#xff09;和 Dire&#xff08;夜魘&#xff09; Dota2 參議院由來自兩派的參議員組成。現在參議院希望對一個 Dota2 游戲里的改變作出決定。他們以一個基于輪為過程的投票進行。在每一輪中&#xff0c;每一…

使用python實現視頻播放器(支持拖動播放位置跳轉)

使用python實現視頻播放器&#xff08;支持拖動播放位置跳轉&#xff09; Python實現視頻播放器&#xff0c;在我早期的博文中介紹或作為資料記錄過 Python實現視頻播放器 https://blog.csdn.net/cnds123/article/details/145926189 Python實現本地視頻/音頻播放器https://bl…

用Python和Pygame創造粉色粒子愛心:3D渲染的藝術

引言 在計算機圖形學中&#xff0c;3D效果的2D渲染是一個迷人的領域。今天&#xff0c;我將分享一個使用Python和Pygame庫創建的粉色粒子愛心效果。這個項目不僅視覺效果驚艷&#xff0c;而且代碼簡潔易懂&#xff0c;非常適合圖形編程初學者學習3D渲染的基礎概念。 項目概述…

在匯編層面理解MESI

理解MESI協議在匯編層面的表現需要結合緩存一致性機制和處理器指令執行的行為。以下是分步驟的解釋&#xff1a; 1. MESI協議基礎 MESI是緩存行&#xff08;Cache Line&#xff09;狀態的協議&#xff0c;定義四種狀態&#xff1a; Modified&#xff08;修改&#xff09;&…

愛瑞編程2025暑期CSP集訓營開始招生啦!

一、什么是暑期CSP集訓營&#xff1f; 為全力備戰2025年9月CSP-J/S認證&#xff0c;舉辦的線下編程集訓活動。 旨在通過高強度編程訓練&#xff0c;幫助學員提升競賽能力&#xff0c;沖刺一等獎。 二、為什么參加集訓營&#xff1f; 高效編程特訓&#xff1a;封閉式學習&…

問題大集10-git使用commit提交中文顯示亂碼

&#xff08;1&#xff09;問題 &#xff08;2&#xff09;解決步驟 1&#xff09; 設置全局編碼為 UTF-8 git config --global core.quotepath false git config --global i18n.commitEncoding utf-8 git config --global i18n.logOutputEncoding utf-8 2&#xff09; 顯示或設…

當AI開始“思考“:大語言模型的文字認知三部曲

引言&#xff1a;從《黑客帝國》說起 1999年上映的科幻經典《黑客帝國》描繪了一個令人震撼的未來圖景——人類生活在一個由人工智能構造的數字矩陣中。當我們觀察現代大型語言模型的工作原理時&#xff0c;竟發現與這個虛構世界有著驚人的相似&#xff1a;人們正在用矩陣以及矩…

Golang改進后的任務調度系統分析

以下是整合了所有改進點的完整代碼實現: package mainimport ("bytes""context""fmt""io""log""net/http""sync""time""github.com/go-redis/redis/v8""github.com/robfig/…

前沿技術有哪些改變生活新趨勢

太陽能技術正在改變的生活 它讓移動設備有了新的能源選擇 太陽能板能直接把陽光轉成電能 這對戶外活動或者電力不便的地方特別有用 比如現在市面上有不少太陽能充電寶 小巧便攜 可以隨時給手機平板充電 需要注意的是 這些設備得放在太陽下才能工作 但它們確實能讓人在野外多用…