Redis 的 SETNX(SET if Not eXists)命令具有原子性,天然適合作為分布式鎖的基礎原語。但單純的 SETNX key value 存在「鎖無法過期釋放」風險,需結合過期時間與獲取邏輯完善。
private static final String LOCK_KEY = "dist:lock:";
private static final long LOCK_TIMEOUT_MS = 60 * 60 * 1000L; // 1 小時
/**
* 獲取分布式鎖
* @param businessKey 業務鍵(如訂單ID)
* @return true 獲取成功,false 獲取失敗
*/
public boolean tryLock(String businessKey) {
String key = LOCK_KEY + businessKey;
long expiresAt = System.currentTimeMillis() + LOCK_TIMEOUT_MS + 1L;
// 1. 嘗試直接獲取鎖
Long acquired = jedis.setnx(key, String.valueOf(expiresAt));
if (acquired == 1L) return true; // 成功
// 2. 獲取失敗,檢查現有鎖是否過期
String currentVal = jedis.get(key);
if (currentVal == null) return tryLock(businessKey); // 極罕見並發:鎖剛被釋放,遞歸重試
long existingExpires = Long.parseLong(currentVal);
if (existingExpires > System.currentTimeMillis()) {
return false; // 鎖未過期,獲取失敗
}
// 3. 鎖已過期,嘗試用 GETSET 原子覆蓋並檢查舊值
String oldVal = jedis.getSet(key, String.valueOf(expiresAt));
if (oldVal == null) return true; // 極罕見:獲取期間鎖被釋放
long oldExpires = Long.parseLong(oldVal);
return oldExpires <= System.currentTimeMillis(); // 只有舊值也過期,才算搶佔成功
}
/**
* 釋放鎖(需確保是鎖持有者才能釋放,生產環境建議用 Lua 腳本保證原子性)
*/
public void unlock(String businessKey) {
String key = LOCK_KEY + businessKey;
// 簡化版:直接刪除。生產環境請用 Lua 腳本判斷 value 後再 del
jedis.del(key);
}
if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) else return 0 endRLock,實現了看門狗、可重入、紅鎖等完整功能。⚠️ 本文代碼為教學演示核心邏輯,高併發生產環境請直接使用 Redisson 或成熟分布式鎖框架。