Redisson 分散式鎖原始碼 01:可重入鎖加鎖

程式設計師小航發表於2021-07-02

前言

相信小夥伴都是使用分散式服務,那一定繞不開分散式服務中資料併發更新問題!

單系統很容易想到 Java 的各種鎖,像 synchronize、ReentrantLock 等等等,那分散式系統如何處理?

當然是使用分散式鎖。

如果小夥伴不知道什麼是分散式鎖,那推薦看看石杉老師的突擊課或者在網上搜一搜相關資料。

當使用 Redis 作為分散式鎖時,當前使用較多的框架就是 Redisson

當然 Redisson 也不僅僅只能當做鎖來使用,也有很多其他的功能,小夥伴們可以看一看官方文件,自己多動手實踐一下。

下面就開始記錄 Redisson 的相關筆記!錯誤之處,歡迎指正。??

環境配置

  • 本地環境搭建的偽叢集:

  • redisson 3.15.6

不同版本可能會有所不同,但是核心思想不會發生太大變化,如果變化很大,希望可以留言。

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.15.6</version>
</dependency>
  • 專案準備

一個簡單的 maven 專案,只需要一個 Main 方法即可。

可重入鎖加鎖

在 lock.lock() 斷點,作為原始碼入口。

預設加鎖,什麼引數也沒有傳遞。但是這裡會設定 leaseTime = -1。這個 leaseTime 的含義是加鎖的時間

剩下的一路挺進即可。

在呼叫 tryAcquire 方法之前,多了一個引數 threadId,是當前執行緒的 id,long 型正數。

非同步加鎖

直接來到 tryAcquireAsync 非同步加鎖方法。

tryAcquireAsync

前面已經說了 leaseTime 是 -1,所以這裡會走到下面的方法中。

至此幾個引數已經清楚:

  1. waitTime:-1;
  2. internalLockLeaseTime:使用預設時間 30000 毫秒;
  3. TimeUnit.MILLISECONDS:單位毫秒;
  4. threadId:執行緒 id;
  5. RedisCommands.EVAL_LONG:eval。

Redis eval 命令的相關文件可以閱讀:https://redis.io/commands/eval

加鎖邏輯

真正的加鎖,其實就是這麼一段 lua 指令碼。

先說明一下 lua 指令碼的引數資訊:

  1. KEYS[1]:getRawName(),加鎖的 key ,比如 anyLock;
  2. ARGV[1]:unit.toMillis(leaseTime),鎖的毫秒時間,比如 30000;
  3. ARGV[2]:getLockName(threadId),是 UUID 和執行緒 id 拼接起來的字串,比如 931573de-903e-42fd-baa7-428ebb7eda80:1。

因為使用的是 lua 指令碼,可以保證這一塊 lua 指令碼的原子性

首次加鎖分析:

  1. exists 命令判斷 redis anyLock 是否存在;
  2. 不存在,使用 hincrby 命令,建立 anyLock 資料;
  3. 對 anyLock 設定過期時間。

加鎖後 Redis 內的資料格式是:

關於 Redis 的 Hash 資料結構可以閱讀:https://redis.io/topics/data-types#hashes

抽象一點可以理解為 anyLock 下面掛著一個 K-V 結構的資料:

"anyLock":{
    "f400aad5-4b1f-4246-a81e-80c2717c3afb:1":"1"
}

執行指令碼

後續的內容就是進行請求執行 lua 指令碼,唯一需要注意的地方就是有個雜湊槽路由。

這塊程式碼是在 CommandAsyncService#evalWriteAsync 方法處呼叫的,是為了獲取一個 NodeSource。

當然這個 NodeSource 裡面只存放了一個 slot(雜湊槽值)。

這個 slot 值是對加鎖的 key 使用 CRC16 演算法計算出來的。

// MAX_SLOT 預設 16384
int result = CRC16.crc16(key.getBytes()) % MAX_SLOT;

這塊計算一個 slot 到底有什麼用呢?

繼續追蹤!

BaseRedisBatchExecutor#addBatchCommandData 在這裡會從 source 裡面獲取到 solt,然後獲得 MasterSlaveEntry。

大概可以理解為這裡是獲取到這個 Redis key 對應的節點。

可重入

既然是可重入鎖,這塊是支援可重入的,來看下可重入是如何保證的。

  1. exists 命令判斷 redis key field 是否存在;
  2. 存在 則通過 hincrby 命令對 key 的 field 對應 value 自增;
  3. 為當前 redis key 設定過期時間。

加鎖互斥

上面已經驗證了兩種情況:

  1. redis key 不存在;
  2. redis key 和 key 的 field 存在。

剩下的情況就是 key 存在的情況下,但是 field 不存在。

要知道 key 的 field 放的是 UUID:ThreadId,說明加鎖的不是當前執行緒。這時候直接返回當前鎖的剩餘時間。

總結

本文主要介紹了 Redisson 可重入鎖的加鎖、鎖重入、鎖互斥邏輯。

核心重點在 lua 指令碼。 同時需要理解 Redis 的 Hash 資料結構。

同時需要記住,在未指定加鎖時間時,預設使用的是 30s。

最後,一張圖介紹本文加鎖邏輯。

相關推薦

相關文章