Redisson 實現分布式讀寫鎖的核心原理是 ?基于 Redis 的 Lua 腳本原子操作? + ?Pub/Sub 通知機制,在保證強一致性的同時實現高效的讀并發(讀不阻塞讀,寫阻塞讀)。以下是其核心設計:
?一、核心數據結構?
Redisson 使用 Redis 的 ?Hash 結構? 存儲鎖信息:
- ?Key:?
{鎖名稱}
(如?my_lock
) - ?Hash 字段:
mode
: 鎖模式(read
/write
)UUID:threadId
: 持有鎖的客戶端標識(如?c983678b-1421-4c76-8ea0-7f3ab7d9c775:1
)count
: 鎖的重入次數(支持可重入)
?二、讀鎖(Read Lock)實現原理?
?1. 獲取讀鎖流程?
-- Lua 腳本原子執行
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;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then -- 當前線程已持有讀鎖redis.call('hincrby', KEYS[1], ARGV[2], 1); -- 重入次數+1redis.call('pexpire', KEYS[1], ARGV[1]); -- 刷新超時return nil;
end;
if (redis.call('hexists', KEYS[1], 'mode') == 1) and (redis.call('hget', KEYS[1], 'mode') == 'read') 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]); -- 存在寫鎖,返回剩余時間(需等待)
?關鍵點?:
- 只要當前無寫鎖(
mode
?非?write
),讀鎖可直接獲取,?不阻塞其他讀鎖。 - 多個讀鎖共享同一個 Hash 結構,通過字段區分不同客戶端。
?2. 讀鎖釋放?
if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then return nil; end; -- 鎖不存在
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); -- 重入次數-1
if (counter == 0) thenredis.call('hdel', KEYS[1], ARGV[2]); -- 移除當前線程的鎖
end;
if (redis.call('hlen', KEYS[1]) == 1) then -- 只剩 mode 字段(無任何鎖)redis.call('del', KEYS[1]); -- 刪除整個 Keyredis.call('publish', KEYS[2], ARGV[1]); -- 發布解鎖通知
end;
return 1;
?三、寫鎖(Write Lock)實現原理?
?1. 獲取寫鎖流程?
if (redis.call('exists', KEYS[1]) == 0) then -- 無任何鎖redis.call('hset', KEYS[1], 'mode', 'write'); -- 設置為寫模式redis.call('hset', KEYS[1], ARGV[2], 1); -- 記錄持有者redis.call('pexpire', KEYS[1], ARGV[1]); -- 設置超時return nil;
end;
if (redis.call('hexists', KEYS[1], 'mode') == 1) and (redis.call('hget', KEYS[1], 'mode') == 'write') then -- 已有寫鎖if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then -- 當前線程持有寫鎖redis.call('hincrby', KEYS[1], ARGV[2], 1); -- 重入次數+1redis.call('pexpire', KEYS[1], ARGV[1]);return nil;end;
end;
return redis.call('pttl', KEYS[1]); -- 存在讀鎖或其他寫鎖,返回剩余時間(需等待)
?關鍵點?:
- 寫鎖要求絕對互斥:?必須無任何鎖(讀/寫)存在才能獲取。
- 若存在讀鎖或其他寫鎖,客戶端需等待(通過 Pub/Sub 監聽解鎖通知)。
?2. 寫鎖釋放?
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil; end; -- 鎖不存在
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); -- 重入次數-1
if (counter == 0) thenredis.call('hdel', KEYS[1], ARGV[3]); -- 移除持有者
end;
if (redis.call('hlen', KEYS[1]) == 1) then -- 只剩 mode 字段redis.call('del', KEYS[1]); -- 刪除 Keyredis.call('publish', KEYS[2], ARGV[1]); -- 發布解鎖通知
end;
return 1;
?四、阻塞等待與通知機制?
?1. 鎖競爭時的等待策略?
- 當鎖獲取失敗時,Redisson ?不輪詢,而是通過 Redis 的 ?Pub/Sub 訂閱鎖釋放事件?:
// 偽代碼:訂閱解鎖通知 RedisPubSub listener = new RedisPubSub() {void onMessage(String channel, String message) {if (message.equals("unlock_msg")) {tryAcquireLock(); // 收到通知后重新嘗試獲取鎖}} }; redis.subscribe(listener, "lock_channel");
- ?優勢?:避免頻繁輪詢 Redis,減少網絡開銷。
?2. 鎖超時與續期?
- ?看門狗機制(Watchdog)??:
后臺線程每隔 10 秒檢查鎖是否仍被持有,若持有則刷新 TTL(默認 30 秒),防止業務未完成時鎖過期。if (lockAcquired) {scheduleExpirationRenewal(threadId); // 啟動看門狗線程 }
?五、公平鎖實現?
Redisson 還提供公平讀寫鎖?(按請求順序獲取鎖):
- 使用 Redis ?List 結構作為請求隊列。
- 每個客戶端獲取鎖前在隊列尾部追加自己的請求 ID。
- 只有隊首的請求有權嘗試獲取鎖,避免饑餓問題。
?總結:Redisson 讀寫鎖的核心優勢?
- ?讀讀并發?:通過 Hash 結構疊加讀鎖計數,無寫鎖時讀操作永不阻塞。
- ?原子性?:所有鎖操作通過 Lua 腳本在 Redis 單線程中執行,無競態條件。
- ?低開銷等待?:基于 Pub/Sub 的事件通知取代輪詢。
- ?容錯性?:鎖超時自動釋放 + 看門狗續期,避免死鎖。
- ?可重入?:支持同一線程多次加鎖(通過?
count
?字段實現)。
?注?:實際代碼比上述偽代碼更復雜(含重試機制、異常處理等),但核心邏輯一致。建議直接閱讀?Redisson 源碼?中的?
RedissonReadLock
?和?RedissonWriteLock
?類。