Redisson 如何實現分布式鎖?(核心原理與思考)
Redisson 是一個功能強大的 Redis 客戶端,它提供了許多分布式對象和服務,其中就包括分布式鎖。Redisson 的分布式鎖是基于 Redis 的 Lua 腳本實現的,這保證了操作的原子性。
我們來一步步拆解 Redisson 鎖的實現原理
4.1 最基礎的 Redis 鎖(存在的問題)
如果只用 SETNX
和 DEL
:
- 加鎖:
SETNX mylock_key my_client_id
my_client_id
是一個唯一標識,比如 UUID,用來標識是哪個客戶端加的鎖。
- 解鎖:
DEL mylock_key
問題:
- 死鎖: 如果客戶端加鎖后,還沒來得及
DEL
就宕機了,那么mylock_key
永遠不會被刪除,其他客戶端就永遠拿不到鎖了。 - 誤刪: 如果客戶端A加鎖,但因為網絡延遲等原因,鎖過期了(被Redis自動刪除了),然后客戶端B加鎖成功。此時客戶端A恢復,執行
DEL mylock_key
,它刪除了客戶端B的鎖!
4.2 Redisson 的改進:過期時間 + 唯一ID + Lua 腳本
Redisson 解決了上述問題,它的核心思想是:
- 加鎖時設置過期時間: 避免死鎖。
- 加鎖時設置唯一ID: 避免誤刪。
- 使用 Lua 腳本: 保證加鎖和解鎖操作的原子性。
Redisson 加鎖的 Lua 腳本(簡化版):
-- KEYS[1]: 鎖的名稱 (e.g., "myLock")
-- ARGV[1]: 鎖的過期時間 (e.g., 30000 毫秒)
-- ARGV[2]: 客戶端的唯一ID (e.g., UUID + 線程ID)-- 如果鎖不存在,或者鎖是當前客戶端加的
if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[2]) == 1 then-- 設置鎖,并設置過期時間-- HINCRBY: 將哈希表中字段的值增加指定增量。這里是記錄重入次數redis.call('hincrby', KEYS[1], ARGV[2], 1);redis.call('pexpire', KEYS[1], ARGV[1]); -- 設置過期時間 (毫秒)return nil; -- 表示加鎖成功
end;
return redis.call('pttl', KEYS[1]); -- 返回鎖的剩余過期時間,表示加鎖失敗
Redisson 解鎖的 Lua 腳本(簡化版):
-- KEYS[1]: 鎖的名稱
-- ARGV[1]: 客戶端的唯一ID-- 如果鎖不存在,或者不是當前客戶端加的鎖
if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 0 thenreturn nil; -- 表示解鎖失敗或鎖不存在
end;-- 減少重入次數
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1);
-- 如果重入次數歸零,說明完全釋放了鎖
if counter > 0 then-- 重新設置過期時間redis.call('pexpire', KEYS[1], ARGV[2]); -- ARGV[2] 是新的過期時間return 0; -- 表示鎖還在,只是重入次數減少
else-- 重入次數歸零,刪除鎖redis.call('del', KEYS[1]);return 1; -- 表示鎖已完全釋放
end;
return nil;
思考:為什么用 Hash 類型?
HINCRBY
:這實現了可重入鎖。同一個客戶端(同一個線程)可以多次獲取同一個鎖,每次獲取都會增加哈希表中對應字段的值(重入次數)。釋放鎖時,每次減少重入次數,直到為0才真正釋放鎖。- 哈希表的字段是客戶端的唯一ID,值是重入次數。
- 鎖的類型:
- 可重入鎖 (Reentrant Lock): 最常用,上面已解釋。
- 公平鎖 (Fair Lock): 保證獲取鎖的順序。
- 聯鎖 (MultiLock): 同時獲取多個 Redis 實例上的鎖,只要有一個實例加鎖失敗,所有已加的鎖都會被釋放。用于解決 Redis 單點故障問題。
- 紅鎖 (RedLock): Redisson 實現了 Redis 官方推薦的 RedLock 算法。它需要多個獨立的 Redis Master 節點(至少3個,通常5個)。客戶端嘗試在大多數(N/2 + 1)Redis 實例上獲取鎖,才能算成功。這提供了更高的可用性和容錯性,但實現更復雜,性能開銷也更大。
- 讀寫鎖 (ReadWriteLock): 允許多個讀操作同時進行,但寫操作是獨占的。
- 信號量 (Semaphore): 控制并發訪問資源的數量。
- 閉鎖 (CountDownLatch): 類似 Java 的 CountDownLatch。