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)});}

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

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

相關文章

kubernetes|云原生|Deployment does not have minimum availability 的解決方案(資源隱藏的由來)

前言&#xff1a; 最近在部署prometheus的過程中遇到的這個問題&#xff0c;感覺比較的經典&#xff0c;有必要記錄一下。 現象是部署prometheus主服務的時候&#xff0c;看不到pod&#xff0c;只能看到deployment&#xff0c;由于慌亂&#xff0c;一度以為是集群有毛病了&am…

c# 基礎語法

c# 程序結構 using System.Collections.Generic; namespace demo1; //一個命名空間可以包含多個類 using System.IO; using System.Drawing;class proj {/// <summary>/// c#是微軟開發的&#xff0c;基于c和c的一種面象對象編程語言&#xff0c;用于快速開發windows桌…

真實網絡中的 bbr

本文包含中心極限定理&#xff0c;大數定律&#xff0c;經濟規律等&#xff0c;bbr 倒沒多少&#xff0c;不過已經習慣把 bbr 當靶子了。 上周寫了 揭秘 bbr 以及 搶帶寬的原理&#xff0c;我對自己說&#xff0c;這都是理論上如何&#xff0c;可實際上呢。于是有必要結合更實際…

ubuntu cutecom串口調試工具使用方法(圖形界面)

文章目錄 Ubuntu下使用CuteCom進行串口調試使用指南什么是CuteCom&#xff1f;主要特點 安裝CuteCom使用APT包管理器從源碼編譯安裝 配置串口CuteCom界面解析&#xff08;啟動cutecom&#xff09;使用CuteCom進行數據發送和接收配置串口參數數據接收數據發送 高級功能和技巧流控…

Vatee萬騰的數字化掌舵:Vatee科技解決方案的全面引領

隨著數字化時代的到來&#xff0c;Vatee萬騰憑借其卓越的科技實力和全面的解決方案&#xff0c;成功地在數字化探索的航程中掌舵引領。 首先&#xff0c;Vatee萬騰以其強大的數字化科技實力成為行業的引領者。vatee萬騰不僅在人工智能、大數據分析、云計算等前沿領域取得了顯著…

PLC通過Modbus轉Profinet網關連接安華變頻器通訊控制電機案例

背景&#xff1a;近年來&#xff0c;隨著自動化技術的不斷進步&#xff0c;Modbus與Profinet之間的轉換成為了許多工廠和企業普遍關注的問題。 Modbus轉Profinet網關作為兩個不同協議設備連接的橋梁&#xff0c;安華變頻器作為一種電氣設備&#xff0c;能夠改變電源的頻率和電…

負載均衡lvs

簡介 ipvsadm 是 Linux 內核中的 IP 虛擬服務器&#xff08;IPVS&#xff09;管理工具。IPVS是 Linux 內核提供的一種負載均衡解決方案&#xff0c;它允許將入站的網絡流量分發到多個后端服務器&#xff0c;以實現負載均衡和高可用性。IPVS通過在內核中維護一個虛擬服務器表&a…

Pytorch中的tensor維度理解

Pytorch中的tensor維度理解 文章目錄 Pytorch中的tensor維度理解摘要打消心理恐懼&#xff0c;從三維學起三維tensor參考文獻 摘要 面對pytorch編程中的tensor時&#xff0c;我不時會感到恐懼。對里面數據是怎么排布的&#xff0c;一直沒有一個直觀的理解。今天我想把這個事情…

springboot(ssm中醫學習服務管理系統 醫學生在線學習平臺Java(codeLW)

springboot(ssm中醫學習服務管理系統 醫學生在線學習平臺Java(code&LW) 開發語言&#xff1a;Java 框架&#xff1a;ssm/springboot vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服務器&#xff1a;tomcat 數據庫&#xff1a;mysql 5.7&#xff08;或…

基于VM虛擬機下Ubuntu18.04系統,Hadoop的安裝與詳細配置

參考博客&#xff1a; https://blog.csdn.net/duchenlong/article/details/114597944 與上面這個博客幾乎差不多&#xff0c;就是java環境配置以及后面的hadoop的hdfs-site.xml文件有一些不同的地方。 準備工作 1.更新 # 更新 sudo apt update sudo apt upgrade2.關閉防火…

MS2401隔離Σ-Δ調制器,可替代ADI的AD7401

產品簡述 MS2401 是一款二階 Σ-Δ 調制器&#xff0c;集成片上數字隔離器&#xff0c;能 將模擬輸入信號轉換為高速 1 位碼流。調制器對輸入信號連續 采樣&#xff0c;無需外部采樣保持電路。模擬信號輸入滿量程為 320 mV &#xff0c;轉換后的數字碼流的最高數據速率為 2…

C++ Boost Thread 編程總結

1.前言 標準C線程即將到來。CUJ預言它將衍生自Boost線程庫&#xff0c;現在就由Bill帶領我們探索一下Boost線程庫。 就在幾年前&#xff0c;用多線程執行程序還是一件非比尋常的事。然而今天互聯網應用服務程序普遍使用多線程來提高與多客戶鏈接時的效率&#xff1b;為了達到最…

統計voc格式數據中的xml標簽、bndbox到excel表格中

有這么個需求是將xml的內容: 1,filename 2.label 3.bndbox:xmin,xmax,ymin,ymax。 … 將這些東西寫入excel表格中,方便我統計標簽數量和框的分布! 于是撰寫了腳本:xml2csv.py 我的xml文件形式如下。大家的目標檢測格式大同小異! <annotation><folder>UAV_d…

【MySQL】多表查詢、子查詢、自連接、合并查詢詳解,包含大量示例,包你會。

復合查詢 前言正式開始一些開胃菜多表查詢自連接子查詢單行子查詢多行子查詢in關鍵字all關鍵字any關鍵字多列子查詢在from中使用子查詢 合并查詢union 和 union all 前言 我前面博客講的所有的查詢都是在單表中進行的&#xff0c;從這里開始就要專門針對查詢這個話題進行進一步…

ansible學習

一文掌握 Ansible 自動化運維 - 知乎 ansible的安裝與簡單的使用_堅持到所有人都放棄!!!的技術博客_51CTO博客

GIT | 基礎操作 | 初始化 | 添加文件 | 修改文件 | 版本回退 | 撤銷修改 | 刪除文件

GIT | 基礎操作 | 初始化 | 添加文件 | 修改文件 | 版本回退 | 撤銷修改 | 刪除文件 文章目錄 GIT | 基礎操作 | 初始化 | 添加文件 | 修改文件 | 版本回退 | 撤銷修改 | 刪除文件前言一、安裝git二、git基本操作2.1 初始化git2.2 配置局部生效2.3 配置全局生效 三、認識工作區…

淺談堆和棧內存以及編程語言

淺談堆和棧內存以及編程語言 棧和堆C 和 C# 的區別&#xff1a;C#總結 編程語言C匯編語言&#xff08;Assembly Language&#xff09;&#xff1a;機器語言&#xff08;Machine Language&#xff09;&#xff1a; 拓展C#依賴注入&#xff08;Dependency Injection&#xff09;模…

2018年全國碩士研究生入學統一考試管理類專業學位聯考數學試題——解析版

文章目錄 2018 年考研管理類聯考數學真題一、問題求解&#xff08;本大題共 5 小題&#xff0c;每小題 3 分&#xff0c;共 45 分&#xff09;下列每題給出 5 個選項中&#xff0c;只有一個是符合要求的&#xff0c;請在答題卡上將所選擇的字母涂黑。真題&#xff08;2018-01&a…

DRF-項目-(1):構建純凈版的drf項目,不再使用django的后臺管理,django的認證,django的session等功能,作為一個純接口項目

項目的目錄結構&#xff1a; -HeartFailure |-- apps |--user |--HeartFailure |-- static |--manage.py 一、django項目相關的 1、命令行中創建django項目 #1、切換到指定的虛擬環境中 workon my_drf#2、該虛擬環境已經安裝好django和rest_framework了 django-admin startp…

補充:linux rsyslog配置多端口監聽(基于UDP)

rsyslog默認udp監聽端口為514,我們可以配置rsyslog基于udp的多端口監聽,實現監控的豐富性 1.環境信息 環境信息 HostnameIpAddressOS versionModuleNotersyslog1192.168.10.246Red Hat Enterprise Linux Server release 7.7 (Maipo)rsyslogd 8.24.0-38.el7linux基礎配置 Li…