Redisson分布式鎖源碼解析、集群環境存在的問題

一、使用Redisson步驟

Redisson各個鎖基本所用Redisson各個鎖基本所用Redisson各個鎖基本所用

二、源碼解析

lock鎖

1) 基本思想:

lock有兩種方法 一種是空參 ?另一種是帶參
? ? ? ? ?* 空參方法:會默認調用看門狗的過期時間30*1000(30秒)
? ? ? ? ?* 然后在正常運行的時候,會啟用定時任務調用重置時間的方法(間隔為開門看配置的默認過期時間的三分之一,也就是10秒)
? ? ? ? ?* 當出現錯誤的時候就會停止續期,直到到期釋放鎖或手動釋放鎖
? ? ? ? ?* 帶參方法:手動設置解鎖時間,到期后自動解鎖,或者業務完成后手動解鎖,不會自動續期

源碼:

Lock

調用lockInterruptibly()方法會默認傳入lease 為-1,該值再后面起作用

  public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();//獲取該鎖的過期時間,如果該鎖沒被持有,會返回一個null,如果被持有 會返回一個過期時間Long ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl != null) {//ttl不為null,說明鎖已經被搶占了RFuture<RedissonLockEntry> future = this.subscribe(threadId);this.commandExecutor.syncSubscription(future);try {//開始循環獲取鎖while(true) {//剛進如循環先嘗試獲取鎖,獲取成功返回null,跳出循環,獲取失敗,則繼續往下走ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl == null) {return;}if (ttl >= 0L) {//如果過期時間大于0,則調用getLatch// 返回一個信號量,開始進入阻塞,阻塞時長為上一次鎖的剩余過期時長,并且讓出cup//有阻塞必然有喚醒,位于解鎖操作中this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {this.getEntry(threadId).getLatch().acquire();}}} finally {this.unsubscribe(future, threadId);}}}

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {//如果leaseTime != -1,即不等于默認值,則表示手動設置了過期時間if (leaseTime != -1L) {return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//如果leaseTime = -1,表示使用默認方式,即使用看門狗默認實現自動續期RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);ttlRemainingFuture.addListener(new FutureListener<Long>() {public void operationComplete(Future<Long> future) throws Exception {//如果tryLockInnerAsync執行成功if (future.isSuccess()) {//獲取過期時間Long ttlRemaining = (Long)future.getNow();//過期時間為空,表示加鎖成功if (ttlRemaining == null) {//開啟刷新重置過期時間步驟RedissonLock.this.scheduleExpirationRenewal(threadId);}}}});return ttlRemainingFuture;}}

//  lua腳本嘗試搶占鎖,失敗返回鎖過期時間<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {this.internalLockLeaseTime = unit.toMillis(leaseTime);//直接使用lua腳本發起命令//通過lua腳本可以看出,redisson加鎖除了使用自定義的名字以外,還要使用uuid// 加上當前線程的threadId組合,以自定義名字作hash的key,使用return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command,//如果該鎖未被占有,則設置鎖,設置過期時間,過期時間為 internalLockLeaseTime ,然后返回null"if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]);return nil; end; " +//如果鎖已經被占有,判斷是否是重入鎖,如果是重入鎖,則將value增加1 ,代表重入,并且設置過期時間,返回null。"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; " +//如果已經被站有所,且不是重入鎖,則返回過期時間"return redis.call('pttl', KEYS[1]);",Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});}

看門狗續命

//看門狗續命機制private void scheduleExpirationRenewal(final long threadId) {//首先會判斷該線程是否已經再重置時間的map中,僅僅第一次進來是空的。if (!expirationRenewalMap.containsKey(this.getEntryName())) {//使用了看門狗默認的時間(30秒) 除以3 ,也就是延遲10秒后執行Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {public void run(Timeout timeout) throws Exception {//判斷是否該線程是否還持有鎖,如果持有,返回1,并且設置過期時間,如果沒持有,返回0RFuture<Boolean> future = RedissonLock.this.commandExecutor.evalWriteAsync(RedissonLock.this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;",Collections.singletonList(RedissonLock.this.getName()), new Object[]{RedissonLock.this.internalLockLeaseTime, RedissonLock.this.getLockName(threadId)});future.addListener(new FutureListener<Boolean>() {public void operationComplete(Future<Boolean> future) throws Exception {//從map中移除該線程,這樣下次再調用該方法仍然可以執行RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());if (!future.isSuccess()) {RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());} else {if ((Boolean)future.getNow()) {//當lua腳本返回1表是true,也就是仍然持有鎖,則遞歸調用該方法,RedissonLock.this.scheduleExpirationRenewal(threadId);}}}});}}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);if (expirationRenewalMap.putIfAbsent(this.getEntryName(), task) != null) {task.cancel();}}}

2、unlock

源碼

    public RFuture<Void> unlockAsync(final long threadId) {final RPromise<Void> result = new RedissonPromise();//調用lua腳本釋放鎖RFuture<Boolean> future = this.unlockInnerAsync(threadId);future.addListener(new FutureListener<Boolean>() {public void operationComplete(Future<Boolean> future) throws Exception {if (!future.isSuccess()) {result.tryFailure(future.cause());} else {Boolean opStatus = (Boolean)future.getNow();//如果鎖狀態為null,表示存在異常,為正常釋放鎖之前,被別人占領鎖了if (opStatus == null) {IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + RedissonLock.this.id + " thread-id: " + threadId);result.tryFailure(cause);} else {//如果返回0.為false 表示可重入鎖,不取消重置過期時間,//返回1 為true,表示已解鎖,取消重置過期時間if (opStatus) {RedissonLock.this.cancelExpirationRenewal();}//解鎖result.trySuccess((Object)null);}}}});return result;}

protected RFuture<Boolean> unlockInnerAsync(long threadId) {return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,//當key不存在,表示鎖未被持有,說明不用解鎖了,返回1 ,1在后續表示取消重置過期時間"if (redis.call('exists', KEYS[1]) == 0) then redis.call('publish', KEYS[2], ARGV[1]); return 1; end;" +//key存在,但是持有鎖的線程不是當前線程,返回null,后面會提出一個異常"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;end; " +//鎖狀態-1后仍然大于0,表示可重入鎖,仍處于鎖定狀態,返回0,0在后續表示 不做處理,仍然重置過期時間"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0; " +//返回鎖狀態不大于0,正常解鎖,返回1,1在后續表示取消重置過期時間"else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1; end; " +"return nil;", Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.unlockMessage, this.internalLockLeaseTime, this.getLockName(threadId)});}

三、集群環境下潛在問題

在Redis主從架構+哨兵模式的環境下,業務系統已經成功獲取了鎖,redis寫入數據,但是正要往從庫上存數據時,發生主庫宕機的情況,從庫在哨兵的選舉下成為了主庫,而另外一個業務請求再次需要獲取鎖,會直接訪問到新的主庫,而此時新主庫是沒有鎖信息的,此時就會出現業務重復的情況。

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

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

相關文章

內網穿透的應用-如何在本地安裝Flask,以及將其web界面發布到公網上并進行遠程訪問

輕量級web開發框架&#xff1a;Flask本地部署及實現公網訪問界面 文章目錄 輕量級web開發框架&#xff1a;Flask本地部署及實現公網訪問界面前言1. 安裝部署Flask2. 安裝Cpolar內網穿透3. 配置Flask的web界面公網訪問地址4. 公網遠程訪問Flask的web界面 前言 本篇文章講解如何…

linux環境下samba服務器的配置

linux服務器怎么創建用戶 在Linux服務器上&#xff0c;可以使用以下步驟創建用戶&#xff1a; 使用adduser命令創建新用戶&#xff1a; sudo adduser username將 username 替換為你要創建的用戶名。這個命令會提示你輸入新用戶的密碼以及其他相關信息。 如果需要為新用戶設…

qml PathPercent使用介紹

PathPercent 是一個QML類型,它表示 Path 上的一個百分比位置。這個類型通常在 PathAnimation 或 PathInterpolator 中使用,以便在路徑上產生一個特定的位置。它提供了一種方式來表示在 Path 元素上的某個點。通過 PathPercent,你可以指定一個百分比,來表示沿著路徑的位置,…

『亞馬遜云科技產品測評』活動征文|通過Lightsail搭建個人筆記

提示&#xff1a;授權聲明&#xff1a;本篇文章授權活動官方亞馬遜云科技文章轉發、改寫權&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒體平臺&#xff0c;第三方開發者媒體等亞馬遜云科技官方渠道 文章目錄 前言實踐知識儲備Lightsail介紹Leanote介紹實踐…

系統架構設計: 21 論敏捷軟件開發方法及其應用

論敏捷軟件開發方法及其應用 請圍繞“敏捷軟件開發方法及其應用”論題,依次從以下三個方面進行論述。 ①簡述你所參與開發的運用了敏捷技術的項目,以及你所擔任的工作; ②分析并討論敏捷<

VSCode插件koroFileHeader的使用。

文章目錄 前言一、koroFileHeader是什么&#xff1f;二、使用步驟1.安裝1.配置2.食用 前言 今天的天氣還不錯&#xff0c;真是金風玉露一相逢&#xff0c;便勝卻人間無數&#xff0c;寫篇博客玩玩&#xff0c;主題&#xff1a;注釋。注釋的本質就是對代碼的解釋和說明&#xf…

nginx 配置靜態緩存全教程 (以及靜態緩存文件沒有生成)

一、第一步定義一個緩存目錄設置目錄結構 在 http 模塊下定義(keys_zone 緩存區名&#xff1a;后面是緩存區大小 inactive 不活躍的文件多久清理 max_size 緩存區所占磁盤的上限 use_temp_path 默認關閉&#xff08;有需要自己百度&#xff09;) proxy_cache_path /path/your…

16 Go的反射

概述 在上一節的內容中&#xff0c;我們介紹了Go的并發&#xff0c;包括&#xff1a;Goroutines、Channels、WaitGroups、Mutex、Select等。在本節中&#xff0c;我們將介紹Go的反射。Go語言中的反射是一種在運行時檢查類型信息并操作對象的能力&#xff0c;通過反射&#xff0…

STM32_6(TIM)

TIM定時器&#xff08;第一部分&#xff09; TIM&#xff08;Timer&#xff09;定時器定時器可以對輸入的時鐘進行計數&#xff0c;并在計數值達到設定值時觸發中斷16位計數器、預分頻器、自動重裝寄存器的時基單元&#xff0c;在72MHz計數時鐘下可以實現最大59.65s的定時不僅…

在游戲開發中,實時渲染和離線渲染對于游戲平衡的影響有哪些?

實時渲染和離線渲染對游戲平衡有那些影響呢&#xff1f;在游戲開發中&#xff0c;渲染方式的選擇對游戲的整體表現和玩家體驗有著至關重要的作用。那么&#xff0c;實時渲染和離線渲染究竟有哪些利弊呢&#xff1f; 一、實時渲染 實時渲染&#xff0c;顧名思義&#xff0c;是…

Ubuntu 1.84.2Visual Studio Code 下載配置與vscode查看內存Hex Editor插件,簡單易懂

目錄 前言 一 首先我為啥要重裝Vs Code呢&#xff1f; 二 下載1.84.2Visual Studio Code 三 配置Vscode終端字體 四 安裝插件 前言 這是一篇將老版本的VsCode下載至最新版的博文&#xff0c;從下載到調試全篇 一 首先我為啥要重裝Vs Code呢&#xff1f; 因為我想安裝這個…

網絡安全深入學習第九課——本機信息收集

文章目錄 一、Windows基本信息收集1、查看當前權限2、查看指定用戶的詳細信息3、查看用戶SID4、查看網卡配置5、查看服務器版本\補丁等6、查看系統架構7、查看安裝的軟件及版本8、查看本機服務信息9、查詢進程信息和列表10、查看啟動程序信息11、查看計劃任務12、查看主機開機時…

1、postman的安裝及使用

一、安裝、登錄 1.安裝 下載地址 2.注冊登錄&#xff08;保存云服務進度&#xff09; 二、界面介紹 三、執行接口測試頁面 請求頁簽&#xff1a; 1、params&#xff1a;當是get請求時&#xff0c;通過params傳參 2、authorization&#xff1a;鑒權 3、headers&#xff1…

大數據-之LibrA數據庫系統告警處理(ALM-37000 MPPDBServer數據目錄或Redo目錄缺失)

告警解釋 當出現如下情況時&#xff0c;產生該告警&#xff1a; 數據實例數據目錄被刪除。數據實例Redo目錄&#xff08;pg_xlog&#xff09;被刪除。 告警屬性 告警ID 告警級別 可自動清除 37000 嚴重 是 告警參數 參數名稱 參數含義 ServiceName 產生告警的服務…

OTP語音芯片WTN6系列:多樣化選擇,滿足各種產品應用需求

隨著科技的快速發展&#xff0c;語音芯片已經成為了智能產品中不可或缺的核心組件。在這個領域中&#xff0c;唯創知音OTP語音芯片WTN6系列以其出色的性能和多樣化的選擇&#xff0c;贏得了廣大開發者的青睞。本文將詳細介紹WTN6系列的幾個重要型號及其特點&#xff0c;并為讀者…

idea 26 個天花板技巧

1、 查看代碼歷史版本&#xff1b;2、 調整idea的虛擬內存&#xff1a;&#xff1b;3、 idea設置成eclipse的快捷鍵&#xff1b;4、 設置提示詞忽略大小寫&#xff1b;5、 關閉代碼檢查&#xff1b;6、 設置文檔注釋模板&#xff1b;7、 顯示方法分隔符&#xff1b;8、 設置多行…

DAOS低時延與高性能RDMA網絡

什么是RDMA RDMA&#xff08;Remote Direct Memory Access&#xff09;遠程直接內存訪問是一種技術&#xff0c;它使兩臺聯網的計算機能夠在主內存中交換數據&#xff0c;而無需依賴任何一臺計算機的處理器、緩存或操作系統。與基于本地的直接內存訪問 ( DMA ) 一樣&#xff0c…

C++ Qt屬性Property使用介紹

文章目錄 C++ 普通類使用Qt屬性Qt窗口類使用屬性自定義控件使用屬性在 Qt 中,屬性(Property)系統提供了一種機制來定義對象的屬性。這些屬性可以在運行時動態地查詢和修改,而且可以通過設計師工具進行可視化編輯。Qt 屬性系統還支持屬性綁定、動畫和其他高級特性。 屬性在…

DFS連通塊問題

DFS連通塊問題 只要是位置的上下左右都有 ‘1’ 的就是同一塊連通塊 一般題目類型有&#xff1a; 連通塊中數量最多的連通塊聯通塊的數量 110010000011111110101001001001101010111011011011101001111110 010000000001010001101100000010010110001111100010101100011110 00…

5-linux-用戶操作、su和sudo、普通權限、特殊權限、解壓壓縮、軟件管理(rpm和yum)、源碼安裝nginx、系統服務、進程管理

1 用戶操作 2 su和sudo 3 普通權限 4 特殊權限 5 解壓壓縮 6 軟件管理&#xff0c;rpm和yum 6.1 rpm 6.2 yum 7 源碼安裝nginx 8 系統服務 9 進程管理 1 用戶操作 ####創建用戶####1 創建sa和sutdents組 groupadd sa groupadd students # 2 用戶可以屬于多個組&#xff0c;只…