從 Martin Kleppmann 的批判到 Redisson 實戰:徹底搞懂分散式鎖與「時間」的陷阱
導言:你也許並不擁有「現在」
在面試中,當被問到「如何實現分散式鎖?」時,90% 的候選人會自信地回答:「用 Redis 的 SETNX 或者 Redisson。」
但如果面試官追問:「如果你的 JVM 發生了 Full GC,導致鎖過期了,但你的程式還在執行,會發生什麼?」這時候,才是區分「碼農」與「工程師」的關鍵時刻。
今天,我不只是教你怎麼用 API,而是要帶你穿越 Martin Kleppmann 與 Redis 作者 Antirez 的那場世紀論戰,從哲學的高度看工程的實踐,手把手帶你寫出能防禦「時鐘漂移」與「GC 暫停」的防禦性代碼。
第一章:時間的幻象與 GC 的致命暫停
在分散式系統中,我們最大的敵人不是網路斷線,而是**「時間」的不確定性**。
1.1 致命的場景:GC 導致的腦裂
想像一下,你的服務拿到了鎖,準備修改訂單。突然,JVM 觸發了 Stop-the-World (STW) 的垃圾回收。
你的服務:以為時間只過了 1ms,實際上世界已經過了 30 秒。
Redis:鎖過期了,把鎖給了別的請求。
結果:兩個執行緒同時操作同一筆數據,數據損壞(Data Corruption)。
讓我們用這張 Mermaid 時序圖 來還原這個災難現場:
程式碼片段
1.2 Kleppmann 的批判
Martin Kleppmann(《Designing Data-Intensive Applications》作者)指出:RedLock 依賴於「系統時鐘」的同步,這在物理上是不可靠的。
如果你的業務只追求效率(如重複發送郵件),RedLock 沒問題;但如果涉及正確性(如金流、庫存),這種依賴時間的鎖是危險的。
第二章:Fencing Tokens — 守護數據的絕對防線
既然我們無法阻止鎖過期,也無法阻止 GC,那我們該如何保護數據?
答案是:讓儲存層(資料庫)變聰明。我們引入 Fencing Token(柵欄權杖) 1。
2.1 機制原理
鎖服務:每次授予鎖時,生成一個單調遞增的數字(Token)。
客戶端:拿著這個 Token 去資料庫更新。
資料庫:檢查這次的 Token 是否比上次處理的大。如果小於等於上次的,直接拒絕。
這就像給資料庫裝了一個「單向棘輪」,時間只能往前走,不能回頭。
程式碼片段
第三章:Redisson 實戰 — 你可能用錯了鎖
很多資深工程師以為用了 Redisson 就萬事大吉,但魔鬼藏在細節裡。
3.1 RedissonMultiLock 不是 RedLock
誤區:很多人以為
RedissonMultiLock就是 RedLock 算法。真相:它只是一個容器,把多個鎖打包而已。它不具備 Fencing Token 機制。
3.2 正確的工具:RFencedLock
Redisson 聽到了社群的聲音,引入了 RFencedLock。它會在鎖獲取成功時,返回一個 Token。
面試必考點:
為什麼要用 RFencedLock?
答:因為普通的 RLock 無法區分「鎖過期」和「GC暫停」,而 RFencedLock 提供了單調遞增的 Token,配合 DB 的 CAS 機制可以保證強一致性。
第四章:手把手寫代碼 — Spring Boot + 函數式編程
光說不練假把式。我們要寫出架構師級別的代碼:優雅、防禦性強、易於測試。
我們將使用「函數式編程 (Functional Programming)」風格,把複雜的鎖邏輯封裝起來,讓業務代碼保持乾淨。
4.1 定義鎖管理器 (Lock Manager)
這個組件負責處理鎖的獲取、Token 的傳遞、異常處理和鎖的釋放。
Java
import org.redisson.api.RFencedLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@Component
public class DistributedLockManager {
private final RedissonClient redisson;
public DistributedLockManager(RedissonClient redisson) {
this.redisson = redisson;
}
// 定義一個函數介面,強制業務邏輯必須接收 Token
@FunctionalInterface
public interface FencedOperation<R> {
R apply(Long token) throws Exception;
}
/**
* 架構師的封裝:自動處理鎖的獲取、釋放與 Token 傳遞
*/
public <R> R executeFenced(String lockKey, FencedOperation<R> operation) {
// 使用 RFencedLock,這是關鍵!
RFencedLock lock = redisson.getFencedLock(lockKey);
Long token = null;
try {
// 嘗試獲取鎖,等待 5s,鎖 10s 後自動過期
// 注意:這裡返回了 Token
token = lock.tryLockAndGetToken(5, 10, TimeUnit.SECONDS);
if (token == null) {
throw new IllegalStateException("無法獲取鎖,請稍後再試: " + lockKey);
}
// 執行業務邏輯,並將 Token 傳遞進去
return operation.apply(token);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("鎖獲取被中斷", e);
} catch (Exception e) {
throw new RuntimeException("業務邏輯執行失敗", e);
} finally {
// 防禦性編程:只有當前執行緒持有鎖時才釋放
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
4.2 業務層 (Service) 與 資料庫 (Repository)
在 Service 層,我們看不到任何 lock.lock() 或 unlock() 的樣板代碼,只有純粹的業務邏輯。
Service 層:
Java
@Service
public class InventoryService {
// ... 注入 lockManager ...
public void decreaseStock(String productId, int quantity) {
String lockKey = "lock:product:" + productId;
// 使用 Lambda 表達式,優雅地執行鎖內邏輯
lockManager.executeFenced(lockKey, (token) -> {
// 1. 查詢商品
InventoryItem item = repo.findById(productId);
// 2. 庫存檢查
if (item.getStock() < quantity) throw new OutOfStockException();
// 3. 【核心】帶柵欄的更新
// 將 token 傳到底層,讓資料庫做最後的守門員
int rows = repo.updateStockWithFence(productId, quantity, token);
if (rows == 0) {
// 這意味著我們是殭屍進程!資料庫拒絕了我們
throw new ConcurrentModificationException("寫入被拒絕:檢測到過期的鎖持有者");
}
return null;
});
}
}
SQL (MyBatis/JPA):
SQL
UPDATE inventory
SET
stock = stock - :quantity,
last_lock_token = :token -- 更新 Token
WHERE
product_id = :productId
AND last_lock_token < :token; -- 【柵欄檢查】只有 Token 變大才允許寫入
結論:不確定性中的確定性
拿到 OFFER 的關鍵,不在於背誦 RedLock 的步驟,而在於理解為什麼我們要這麼做。
架構師的決策矩陣 (Takeaway)
你的需求 | 推薦方案 | 原因 |
效率優先 (如發送通知) | 普通 | 允許偶爾失敗,性能最好。 |
正確性優先 (如扣庫存) |
| 必須防止 GC 造成的腦裂。 |
極致強一致性 | ZooKeeper / etcd | CP 系統,天生比 Redis (AP) 更適合做鎖。 |
給讀者的最後建議:
最好的鎖,是沒有鎖。如果能設計成冪等 (Idempotent) 的操作(例如資料庫的唯一鍵約束、樂觀鎖版本號),往往比引入複雜的分散式鎖更可靠。
(本文內容基於 Martin Kleppmann 的理論與 Redisson 官方文檔整理)這是一份為你精心整理的技術網誌,採用資深架構師的視角,結合Mermaid 圖表與實戰代碼,旨在幫助讀者深入理解分散式鎖的本質,並在面試中脫穎而出。