圖片分頁wordpress主題seo優(yōu)化技術(shù)排名
Redisson分布式鎖的概念和使用
- 一 簡介
- 1.1 什么是分布式鎖?
- 1.2 Redisson分布式鎖的原理
- 1.3 Redisson分布式鎖的優(yōu)勢
- 1.4 Redisson分布式鎖的應用場景
- 二 案例
- 2.1 鎖競爭案例
- 2.2 看門狗案例
- 2.3 參考文章
前言
這是我在這個網(wǎng)站整理的筆記,有錯誤的地方請指出,關(guān)注我,接下來還會持續(xù)更新。作者:神的孩子都在歌唱
一 簡介
1.1 什么是分布式鎖?
在分布式系統(tǒng)中,多個服務實例或進程可能會同時訪問共享資源(例如數(shù)據(jù)庫、文件等)。為了防止數(shù)據(jù)競爭或一致性問題,我們需要一種機制來確保在同一時間,只有一個進程能夠訪問這些資源。這種機制就是分布式鎖。
Redisson 是一個支持 Redis 的 Java 客戶端,它不僅能提供簡單的 Redis 連接,還包括了許多高級功能,如分布式鎖、異步任務執(zhí)行、限流等。Redisson 基于 Redis 來實現(xiàn)分布式鎖,具備高效、可靠的特性。
1.2 Redisson分布式鎖的原理
Redisson 的分布式鎖主要依賴 Redis 的 SETNX
和 EXPIRE
命令來實現(xiàn)。流程如下:
-
獲取鎖:客戶端通過
SETNX
(SET if Not eXists)命令嘗試在 Redis 中設置一個鍵。如果該鍵不存在,表示沒有其他進程持有鎖,當前進程即可成功獲取鎖。 -
鎖過期時間:為了避免某個持有鎖的進程崩潰而導致鎖無法釋放,通常會為鎖設置一個過期時間(如10秒)。如果超過這個時間鎖還沒有被釋放,Redis 將自動刪除該鎖,允許其他進程獲取。
-
釋放鎖:當任務完成后,進程會通過
DEL
命令來釋放鎖,其他進程就可以重新獲取鎖。 -
可重入鎖:Redisson 提供了可重入鎖,即同一個線程可以多次獲取鎖,而不會被鎖定阻塞。只有當線程完全釋放鎖后,其他線程才能獲取該鎖。
1.3 Redisson分布式鎖的優(yōu)勢
- 高效:Redis 基于內(nèi)存操作,具有極快的響應速度。Redisson 使用 Lua 腳本將獲取和釋放鎖的操作進行原子化操作,避免并發(fā)問題。
- 可靠性:Redisson 通過 Redis 的過期機制和 Watchdog(看門狗)機制,確保鎖可以自動釋放,防止因進程異常退出導致的死鎖問題。
- 可擴展性:Redisson 支持 Redis 集群、哨兵模式等多種模式,適用于不同規(guī)模的分布式系統(tǒng)。
1.4 Redisson分布式鎖的應用場景
-
庫存扣減:在電商系統(tǒng)中,當用戶發(fā)起購買請求時,需要確保在多個并發(fā)請求中,庫存只能被扣減一次,避免超賣現(xiàn)象。
-
任務調(diào)度:在分布式任務調(diào)度系統(tǒng)中,需要保證同一時間只有一個服務實例執(zhí)行某個任務,防止重復執(zhí)行。
-
分布式事務:在分布式事務中,確保在多個服務之間的一致性操作,分布式鎖能夠確保只有一個服務能修改某個共享資源。
二 案例
2.1 鎖競爭案例
假設我有兩個節(jié)點可以執(zhí)行同一種業(yè)務,我需求是節(jié)點1執(zhí)行的時候,節(jié)點2不能執(zhí)行。那么就可以使用分布式鎖實現(xiàn),代碼如下
引入redisson的依賴
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.35.0</version></dependency>
編一個redisson客戶端
public class RedissonConfig {public RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://192.168.1.47:6379").setPassword("xxxx");return Redisson.create(config);}
}
節(jié)點1
package org.example.demo;
import org.example.tool.RedissonConfig;
import org.redisson.api.RKeys;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/*** @Author chenyunzhi* @DATE 2024/9/3 10:01* @Description:*/
public class demo1 {public static void main(String[] args) throws InterruptedException {RedissonConfig redissonConfig = new RedissonConfig();RedissonClient redissonClient = redissonConfig.redissonClient();int i = 0;Thread.sleep(2000);while (i < 5) {try {Date currentDate = new Date();System.out.println("當前時間: " + currentDate);RLock testLock = redissonClient.getLock("testLock");//嘗試獲取鎖 waitTime(重試等待時間),leaseTime(過期時間),TimeUnit(時間單位)boolean b = testLock.tryLock(1, 5, TimeUnit.SECONDS);i++;if (b) {try {Thread.sleep(2000);System.out.println("執(zhí)行業(yè)務" + i + "次 節(jié)點1");} finally {testLock.unlock();}} else {System.out.println("獲取鎖失敗 節(jié)點1");}} catch (Exception e) {System.out.println("鎖中斷 節(jié)點1" + e.getMessage());}}}
}
節(jié)點1完成業(yè)務的速度大約2秒鐘,循環(huán)執(zhí)行5次任務,如果獲取鎖失敗就不執(zhí)行,進入下一次執(zhí)行
節(jié)點2
package org.example.demo;
import org.example.tool.RedissonConfig;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/*** @Author chenyunzhi* @DATE 2024/9/3 10:01* @Description:*/
public class demo2 {public static void main(String[] args) throws InterruptedException {RedissonConfig redissonConfig = new RedissonConfig();RedissonClient redissonClient = redissonConfig.redissonClient();int i = 0;while (i < 5) {try {Date currentDate = new Date();System.out.println("當前時間: " + currentDate);RLock testLock = redissonClient.getLock("testLock");//嘗試獲取鎖 waitTime(重試等待時間),leaseTime(過期時間),TimeUnit(時間單位)boolean b = testLock.tryLock(1, 10, TimeUnit.SECONDS);i++;if (b) {try {Thread.sleep(1000);System.out.println("執(zhí)行業(yè)務" + i + "次 節(jié)點2");} finally {testLock.unlock();}} else {System.out.println("獲取鎖失敗 節(jié)點2");}} catch (Exception e) {System.out.println("鎖中斷 節(jié)點2" + e.getMessage());}}}
}
節(jié)點2完成業(yè)務的速度大約1秒鐘,循環(huán)執(zhí)行5次任務,如果獲取鎖失敗就不執(zhí)行,進入下一次執(zhí)行
然后我們同時允許兩個節(jié)點行測試
通過上面測試可以發(fā)現(xiàn),在節(jié)點1執(zhí)行業(yè)務的時候,節(jié)點2獲取鎖失敗了,然后無法執(zhí)行業(yè)務,反之也是如此
2.2 看門狗案例
上面的案例我們?yōu)榱朔乐谷蝿账梨i,都會給鎖都設定了有效時間,可是我們不確定這個任務要執(zhí)行多久,就會導致任務還沒執(zhí)行完成,鎖就先過期了。watchDog(看門狗)的作用是可以確保等待某個節(jié)點任務完全執(zhí)行完成后才去釋放鎖。
redisson的看門狗底層使用的是setnx加lua腳本實現(xiàn)的,會定期給鎖續(xù)約,默認是每隔10s續(xù)期一次,一次續(xù)約30s,其他線程在最大等待時間內(nèi)自旋,不斷嘗試獲取鎖,超過最大等待時間則獲取鎖失敗,設置默認加鎖時間的參數(shù)是 lockWatchdogTimeout,會和主線程一起銷毀。。
注意:
- 看門狗只會在鎖沒設定過期時間的時候才有效
- 如果任務一直阻塞,那么鎖就會一直續(xù)期,得不到釋放
package org.example.demo;import org.example.tool.RedissonConfig;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;import java.util.concurrent.TimeUnit;/*** @Author chenyunzhi* @DATE 2024/9/21 11:26* @Description:*/
public class demo3 {public static void main(String[] args) {RedissonConfig redissonConfig = new RedissonConfig();RedissonClient redissonClient = redissonConfig.redissonClient();RLock myLock = redissonClient.getLock("myLock");if (myLock.tryLock()) {try {// 模擬任務執(zhí)行System.out.println("執(zhí)行任務");Thread.sleep(50000); // 模擬50秒的任務System.out.println("任務完成");} catch (InterruptedException e) {e.printStackTrace();} finally {//判斷當前線程是否持有鎖 isHeldByCurrentThreadif (myLock.isHeldByCurrentThread()) {myLock.unlock();System.out.println("釋放鎖");}}}redissonClient.shutdown();}
}
使用myLock.tryLock()不設置過期時間,那么看門狗會默認啟動,然后會默認設置30秒的過期時間,每10秒刷新一次過期時間。
2.3 參考文章
github: https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers
redisson中的看門狗機制總結(jié)
Redis的分布式鎖
redisson watchdog 使用和原理
作者:神的孩子都在歌唱
本人博客:https://blog.csdn.net/weixin_46654114
轉(zhuǎn)載說明:務必注明來源,附帶本人博客連接。