搜尋
首頁Javajava教程詳解redisson實現分散式鎖定方法原理

詳解redisson實現分散式鎖定方法原理

Aug 22, 2017 pm 04:46 PM
redisson分散式方法

Redisson分散式鎖定QySHTML5中文學習網- HTML5先行者學習網

之前的基於註解的鎖定有一種鎖是基本redis的分散式鎖,鎖的實作我是基於redisson元件提供的RLock,這篇來看看redisson是如何實現鎖的。 QySHTML5中文學習網- HTML5先驅學習網

不同版本實現鎖定的機制並不相同QySHTML5中文學習網- HTML5先行者學習網

引用的redisson最近發布的版本3.2.3,不同的版本可能實現鎖的機制並不相同,早期版本好像是採用簡單的setnx,getset等常規命令來配置完成,而後期由於redis支援了腳本Lua變更了實現原理。 QySHTML5中文學習網- HTML5先行者學習網

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.2.3</version></dependency>

setnx需要配合getset以及交易來完成,這樣才能比較好的避免死鎖問題,而新版本由於支援lua腳本,可以避免使用事務以及操作多個redis指令,語意表達更加清晰一些。 QySHTML5中文學習網- HTML5先行者學習網

#RLock介面的特性##QySHTML5中文學習網- HTML5先行者學習網

繼承標準介面LockQySHTML5中文學習網- HTML5先行者學習網

擁有標準鎖定介面的所有特性,如lock,unlock,trylock等等。

QySHTML5中文學習網- HTML5先行者學習網

#擴充標準介面Lock##QySHTML5中文學習網- HTML5先行者學習網擴展了很多方法,常用的主要有:強制鎖釋放,帶有效期的鎖,還有一組非同步的方法。其中前面兩個方法主要是解決標準lock可能造成的死鎖問題。例如某個執行緒取得到鎖之後,執行緒所在機器死機,此時取得了鎖的執行緒無法正常釋放鎖導致其餘的等待鎖的執行緒一直等待下去。

QySHTML5中文學習網- HTML5先行者學習網

#可重入機制##QySHTML5中文學習網- HTML5先行者學習網各版本實現有差異,可重入主要考慮的是性能,同一線程在未釋放鎖時如果再次申請鎖資源不需要走申請流程,只需要將已經獲取的鎖繼續返回而記錄上已經重入的次數即可,與jdk裡面的ReentrantLock功能類似。重入次數靠hincrby指令來配合使用,詳細的參數下面的程式碼。 QySHTML5中文學習網 - HTML5先行者學習網

#怎麼判斷是相同執行緒?

QySHTML5中文學習網- HTML5先行者學習網

redisson的方案是,RedissonLock實例的一個guid再加當前執行緒的id,透過getLockName返回

QySHTML5中文學習網- HTML5先行者學習網

public class RedissonLock extends RedissonExpirable implements RLock { final UUID id; protected RedissonLock(CommandExecutor commandExecutor, String name, UUID id) {  super(commandExecutor, name);  this.internalLockLeaseTime = TimeUnit.SECONDS.toMillis(30L);  this.commandExecutor = commandExecutor;  this.id = id; } String getLockName(long threadId) {  return this.id + ":" + threadId; }

RLock取得鎖定的兩個場景

QySHTML5中文學習網- HTML5先行者學習網這裡拿tryLock的源碼來看:tryAcquire方法是申請鎖定並回傳鎖定有效期還剩餘的時間,如果為空說明鎖未被其它線程申請直接獲取並返回,如果獲取到時間,則進入等待競爭邏輯。 QySHTML5中文學習網- HTML5先行者學習網

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {  long time = unit.toMillis(waitTime);  long current = System.currentTimeMillis();  final long threadId = Thread.currentThread().getId();  Long ttl = this.tryAcquire(leaseTime, unit);  if(ttl == null) {   //直接获取到锁   return true;  } else {   //有竞争的后续看  } }

無競爭,直接取得鎖定

#QySHTML5中文學習網- HTML5先行者學習網先看下首先取得鎖定並釋放鎖定背後的redis都在做什麼,可以利用redis的monitor來在後台監控redis的執行情況。當我們在方法了增加@RequestLockable之後,其實就是呼叫lock以及unlock,以下是redis指令:QySHTML5中文學習網- HTML5先行者學習網

#加鎖定

QySHTML5中文學習網- HTML5先行者學習網由於高版本的redis支援lua腳本,所以redisson也對其進行了支持,採用了腳本模式,不熟悉lua腳本的可以去查找下。執行lua指令的邏輯如下:QySHTML5中文學習網 - HTML5先行者學習網

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {    this.internalLockLeaseTime = unit.toMillis(leaseTime);    return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call(/&#39;exists/&#39;, KEYS[1]) == 0) then redis.call(/&#39;hset/&#39;, KEYS[1], ARGV[2], 1); redis.call(/&#39;pexpire/&#39;, KEYS[1], ARGV[1]); return nil; end; if (redis.call(/&#39;hexists/&#39;, KEYS[1], ARGV[2]) == 1) then redis.call(/&#39;hincrby/&#39;, KEYS[1], ARGV[2], 1); redis.call(/&#39;pexpire/&#39;, KEYS[1], ARGV[1]); return nil; end; return redis.call(/&#39;pttl/&#39;, KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{Long.valueOf(this.internalLockLeaseTime), this.getLockName(threadId)});  }

加锁的流程:QySHTML5中文学习网 - HTML5先行者学习网

  1. 判断lock键是否存在,不存在直接调用hset存储当前线程信息并且设置过期时间,返回nil,告诉客户端直接获取到锁。

  2. 判断lock键是否存在,存在则将重入次数加1,并重新设置过期时间,返回nil,告诉客户端直接获取到锁。

  3. 被其它线程已经锁定,返回锁有效期的剩余时间,告诉客户端需要等待。

"EVAL" "if (redis.call(&#39;exists&#39;, KEYS[1]) == 0) then redis.call(&#39;hset&#39;, KEYS[1], ARGV[2], 1); redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); return nil; end;if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[2]) == 1) then redis.call(&#39;hincrby&#39;, KEYS[1], ARGV[2], 1); redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); return nil; end;return redis.call(&#39;pttl&#39;, KEYS[1]);" "1" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"  "1000" "346e1eb8-5bfd-4d49-9870-042df402f248:21"

上面的lua脚本会转换成真正的redis命令,下面的是经过lua脚本运算之后实际执行的redis命令。QySHTML5中文学习网 - HTML5先行者学习网

1486642677.053488 [0 lua] "exists" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"1486642677.053515 [0 lua] "hset" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0" "346e1eb8-5bfd-4d49-9870-042df402f248:21" "1"1486642677.053540 [0 lua] "pexpire" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0" "1000"

解锁QySHTML5中文学习网 - HTML5先行者学习网

解锁的流程看起来复杂些:QySHTML5中文学习网 - HTML5先行者学习网

  1. 如果lock键不存在,发消息说锁已经可用

  2. 如果锁不是被当前线程锁定,则返回nil

  3. 由于支持可重入,在解锁时将重入次数需要减1

  4. 如果计算后的重入次数>0,则重新设置过期时间

  5. 如果计算后的重入次数

"EVAL" "if (redis.call(&#39;exists&#39;, KEYS[1]) == 0) then redis.call(&#39;publish&#39;, KEYS[2], ARGV[1]); return 1; end;if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[3]) == 0) then return nil;end; local counter = redis.call(&#39;hincrby&#39;, KEYS[1], ARGV[3], -1); if (counter > 0) then redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[2]); return 0; else redis.call(&#39;del&#39;, KEYS[1]); redis.call(&#39;publish&#39;, KEYS[2], ARGV[1]); return 1; end; return nil;""2" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0" "redisson_lock__channel:{lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0}" "0" "1000" "346e1eb8-5bfd-4d49-9870-042df402f248:21"

无竞争情况下解锁redis命令:QySHTML5中文学习网 - HTML5先行者学习网

主要是发送一个解锁的消息,以此唤醒等待队列中的线程重新竞争锁。QySHTML5中文学习网 - HTML5先行者学习网

1486642678.493691 [0 lua] "exists" "lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0"1486642678.493712 [0 lua] "publish" "redisson_lock__channel:{lock.com.csp.product.api.service.ProductAppService.searchProductByPage#0}" "0"

有竞争,等待QySHTML5中文学习网 - HTML5先行者学习网

有竞争的情况在redis端的lua脚本是相同的,只是不同的条件执行不同的redis命令,复杂的在redisson的源码上。当通过tryAcquire发现锁被其它线程申请时,需要进入等待竞争逻辑中。QySHTML5中文学习网 - HTML5先行者学习网

  • this.await返回false,说明等待时间已经超出获取锁最大等待时间,取消订阅并返回获取锁失败

  • this.await返回true,进入循环尝试获取锁。

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {    long time = unit.toMillis(waitTime);    long current = System.currentTimeMillis();    final long threadId = Thread.currentThread().getId();    Long ttl = this.tryAcquire(leaseTime, unit);    if(ttl == null) {      return true;    } else {      //重点是这段      time -= System.currentTimeMillis() - current;      if(time <= 0L) {        return false;      } else {        current = System.currentTimeMillis();        final RFuture subscribeFuture = this.subscribe(threadId);        if(!this.await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {          if(!subscribeFuture.cancel(false)) {            subscribeFuture.addListener(new FutureListener() {              public void operationComplete(Future<RedissonLockEntry> future) throws Exception {                if(subscribeFuture.isSuccess()) {                  RedissonLock.this.unsubscribe(subscribeFuture, threadId);                }              }            });          }          return false;        } else {          boolean var16;          try {            time -= System.currentTimeMillis() - current;            if(time <= 0L) {              boolean currentTime1 = false;              return currentTime1;            }            do {              long currentTime = System.currentTimeMillis();              ttl = this.tryAcquire(leaseTime, unit);              if(ttl == null) {                var16 = true;                return var16;              }              time -= System.currentTimeMillis() - currentTime;              if(time <= 0L) {                var16 = false;                return var16;              }              currentTime = System.currentTimeMillis();              if(ttl.longValue() >= 0L && ttl.longValue() < time) {                this.getEntry(threadId).getLatch().tryAcquire(ttl.longValue(), TimeUnit.MILLISECONDS);              } else {                this.getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);              }              time -= System.currentTimeMillis() - currentTime;            } while(time > 0L);            var16 = false;          } finally {            this.unsubscribe(subscribeFuture, threadId);          }          return var16;        }      }    }  }

循环尝试一般有如下几种方法:QySHTML5中文学习网 - HTML5先行者学习网

  • while循环,一次接着一次的尝试,这个方法的缺点是会造成大量无效的锁申请。

  • Thread.sleep,在上面的while方案中增加睡眠时间以降低锁申请次数,缺点是这个睡眠的时间设置比较难控制。

  • 基于信息量,当锁被其它资源占用时,当前线程订阅锁的释放事件,一旦锁释放会发消息通知待等待的锁进行竞争,有效的解决了无效的锁申请情况。核心逻辑是this.getEntry(threadId).getLatch().tryAcquire,this.getEntry(threadId).getLatch()返回的是一个信号量,有兴趣可以再研究研究。

redisson依赖QySHTML5中文学习网 - HTML5先行者学习网

由于redisson不光是针对锁,提供了很多客户端操作redis的方法,所以会依赖一些其它的框架,比如netty,如果只是简单的使用锁也可以自己去实现。

以上是詳解redisson實現分散式鎖定方法原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具