如何使用Redis 實(shí)現(xiàn)分布式鎖(含自動(dòng)續(xù)期與安全釋放)
用 Redis 實(shí)現(xiàn)分布式鎖(含自動(dòng)續(xù)期與安全釋放)詳解
在分布式系統(tǒng)中,多個(gè)服務(wù)實(shí)例可能同時(shí)操作共享資源(如庫存扣減、訂單生成),為保證數(shù)據(jù)一致性,必須使用 分布式鎖。Redis 憑借其高性能和原子操作能力,成為實(shí)現(xiàn)分布式鎖的常用選擇。
本文將深入講解如何用 Redis 實(shí)現(xiàn)一個(gè) 安全、可重入、帶自動(dòng)續(xù)期(Watchdog)、支持高可用 的分布式鎖,并提供 Java 實(shí)現(xiàn)示例(含 Redisson 與原生 Lua 腳本兩種方式)。
一、分布式鎖的核心要求
| 要求 | 說明 |
|---|---|
| ? 互斥性 | 同一時(shí)間只有一個(gè)客戶端能持有鎖 |
| ? 可重入性 | 同一線程可多次獲取同一把鎖 |
| ? 鎖釋放安全 | 只能由加鎖的客戶端釋放(防誤刪) |
| ? 自動(dòng)續(xù)期(Watchdog) | 防止業(yè)務(wù)執(zhí)行時(shí)間超過鎖過期時(shí)間 |
| ? 高可用 | 支持主從、集群、哨兵模式 |
| ? 高性能 | 加鎖/釋放速度快,不影響業(yè)務(wù) |
二、基礎(chǔ)實(shí)現(xiàn):SET + NX + EX
最簡單的分布式鎖實(shí)現(xiàn):
SET lock:order:12345 "client_1" NX EX 10
NX:key 不存在時(shí)才設(shè)置(保證互斥)EX 10:10秒后自動(dòng)過期(防死鎖)client_1:唯一客戶端標(biāo)識(shí)(用于釋放時(shí)校驗(yàn))
? 問題:無續(xù)期機(jī)制,業(yè)務(wù)執(zhí)行超時(shí)會(huì)自動(dòng)釋放,導(dǎo)致并發(fā)。
三、安全釋放鎖:Lua 腳本防誤刪
直接 DEL 鎖可能誤刪其他客戶端的鎖。應(yīng)使用 Lua 腳本校驗(yàn) value:
? Lua 腳本(unlock.lua)
-- KEYS[1] = lock key
-- ARGV[1] = client_id
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
endJava 調(diào)用示例:
public boolean unlock(String lockKey, String clientId) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
" else " +
" return 0 " +
" end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList(lockKey),
clientId
);
return result != null && result == 1;
}四、自動(dòng)續(xù)期(Watchdog 機(jī)制)
如果業(yè)務(wù)執(zhí)行時(shí)間超過鎖過期時(shí)間,鎖會(huì)被自動(dòng)釋放,導(dǎo)致多個(gè)客戶端同時(shí)進(jìn)入臨界區(qū)。
解決方案:啟動(dòng)一個(gè)后臺(tái)線程(Watchdog),每隔一段時(shí)間檢查鎖是否仍被持有,若持有則延長過期時(shí)間。
Watchdog 工作流程:
客戶端A加鎖(EX 30s)
↓
啟動(dòng) Watchdog 線程(每10s檢查一次)
↓
若鎖仍存在且屬于本客戶端 → 執(zhí)行 EXPIRE lock:xxx 30
↓
業(yè)務(wù)執(zhí)行完成 → 取消續(xù)期 + 釋放鎖
五、完整實(shí)現(xiàn)方案對(duì)比
| 方案 | 是否推薦 | 說明 |
|---|---|---|
| ?? 原生 SET + Lua | ?? 基礎(chǔ)可用 | 需自行實(shí)現(xiàn)續(xù)期、可重入 |
| ?? Redisson(推薦) | ? 強(qiáng)烈推薦 | 內(nèi)置 Watchdog、可重入、公平鎖等 |
| ?? Jedis + 自研 | ? 不推薦 | 容易出錯(cuò),維護(hù)成本高 |
六、使用 Redisson 實(shí)現(xiàn)(推薦方案)
Redisson 提供了開箱即用的分布式鎖,完美支持:
- 可重入
- 自動(dòng)續(xù)期(Watchdog)
- 公平鎖、讀寫鎖
- 高可用(集群/哨兵)
1. 添加依賴
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.24.1</version>
</dependency>2. 獲取鎖并自動(dòng)續(xù)期
@Autowired
private RedissonClient redissonClient;
public void doBusiness() {
RLock lock = redissonClient.getLock("lock:order:12345");
try {
// 嘗試加鎖,最多等待10秒,上鎖后30秒自動(dòng)解鎖
boolean res = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (res) {
try {
// 執(zhí)行業(yè)務(wù)邏輯(可能耗時(shí)較長)
System.out.println("Locked! Doing business...");
Thread.sleep(25000); // 模擬長任務(wù)
} finally {
lock.unlock(); // 自動(dòng)取消續(xù)期 + 安全釋放
}
} else {
System.out.println("Failed to acquire lock");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}3. Redisson 的 Watchdog 原理
- 默認(rèn)鎖過期時(shí)間:
30s - Watchdog 每
10s檢查一次 - 若客戶端仍持有鎖 → 自動(dòng)
EXPIRE lock:xxx 30 - 解鎖時(shí)自動(dòng)取消續(xù)期
? 無需擔(dān)心業(yè)務(wù)超時(shí),只要客戶端存活,鎖就不會(huì)被釋放。
七、原生 Redis + Lua 實(shí)現(xiàn)(學(xué)習(xí)用)
如果你不想使用 Redisson,也可自行實(shí)現(xiàn) Watchdog。
1. 加鎖(SETNX + EX)
public boolean tryLock(String lockKey, String clientId, int expireSeconds) {
String result = redisTemplate.opsForValue()
.setIfAbsent(lockKey, clientId, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}2. 啟動(dòng) Watchdog 線程
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private volatile boolean isLocked = false;
public void watchDog(String lockKey, String clientId, int expireSeconds) {
isLocked = true;
scheduler.scheduleAtFixedRate(() -> {
if (isLocked) {
// 只有當(dāng)前客戶端持有鎖時(shí)才續(xù)期
String current = redisTemplate.opsForValue().get(lockKey);
if (clientId.equals(current)) {
redisTemplate.expire(lockKey, expireSeconds, TimeUnit.SECONDS);
}
}
}, expireSeconds / 3, expireSeconds / 3, TimeUnit.SECONDS);
}3. 釋放鎖(Lua 腳本)
見前文 Lua 腳本實(shí)現(xiàn)。
4. 使用示例
String lockKey = "lock:order:12345";
String clientId = "client_" + Thread.currentThread().getId();
if (tryLock(lockKey, clientId, 30)) {
try {
watchDog(lockKey, clientId, 30); // 啟動(dòng)續(xù)期
// 執(zhí)行業(yè)務(wù)
} finally {
isLocked = false; // 停止續(xù)期
unlock(lockKey, clientId); // 安全釋放
}
}八、可重入鎖實(shí)現(xiàn)思路
可重入鎖需記錄:
- 當(dāng)前持有線程
- 重入次數(shù)
可用 Hash 結(jié)構(gòu)實(shí)現(xiàn):
# 鎖結(jié)構(gòu) lock:order:12345 field: client_1 value: 2 # 重入次數(shù)
加鎖時(shí):
- 若 key 不存在 → 設(shè)置 client_1:1
- 若 key 存在且 field == client_1 → value +1
- 否則失敗
釋放時(shí):
- value -1,為 0 時(shí)刪除 key
九、最佳實(shí)踐與注意事項(xiàng)
| 項(xiàng)目 | 建議 |
|---|---|
| ?? 安全釋放 | 必須使用 Lua 腳本校驗(yàn) client_id |
| ?? 鎖過期時(shí)間 | 設(shè)置合理(如 10~30s),避免過長導(dǎo)致阻塞 |
| ?? 自動(dòng)續(xù)期 | 使用 Redisson 或自研 Watchdog |
| ?? 可重入 | 生產(chǎn)環(huán)境必須支持 |
| ?? 阻塞操作 | 鎖內(nèi)避免網(wǎng)絡(luò)調(diào)用、sleep |
| ?? 監(jiān)控 | 記錄加鎖失敗、等待時(shí)間 |
| ?? 異常處理 | 確保 finally 中釋放鎖 |
| ?? 高可用 | 使用 Redis 集群或哨兵 |
十、常見問題(FAQ)
Q1:Redis 主從切換會(huì)導(dǎo)致鎖失效嗎?
? 會(huì)!主節(jié)點(diǎn)加鎖后未同步到從節(jié)點(diǎn),主節(jié)點(diǎn)宕機(jī),從節(jié)點(diǎn)升主,鎖丟失。
解決方案:
- 使用 Redlock 算法(多個(gè)獨(dú)立 Redis 實(shí)例)
- 使用 ZooKeeper 或 etcd 實(shí)現(xiàn)更安全的分布式鎖
- 多數(shù)場(chǎng)景下,Redisson + 主從已足夠(犧牲 CAP 中的 CP)
Q2:Watchdog 占用資源嗎?
? 占用少量 CPU 和連接,但可接受。Redisson 默認(rèn)只在持有鎖時(shí)啟動(dòng)。
Q3:能用 SETNX + DEL 嗎?
? 不安全!DEL 可能誤刪其他客戶端的鎖。
十一、總結(jié):Redis 分布式鎖實(shí)現(xiàn)方案對(duì)比
| 方案 | 是否可重入 | 自動(dòng)續(xù)期 | 安全釋放 | 推薦度 |
|---|---|---|---|---|
| SETNX + DEL | ? | ? | ? | ? |
| SETNX + Lua | ?? | ? | ? | ??? |
| 自研 Watchdog | ?? | ? | ? | ???? |
| Redisson | ? | ? | ? | ????? |
? 結(jié)語:
使用 Redis 實(shí)現(xiàn)分布式鎖,強(qiáng)烈推薦使用 Redisson。它封裝了復(fù)雜的細(xì)節(jié)(可重入、續(xù)期、安全釋放),讓你像使用本地鎖一樣操作分布式鎖。
?? 核心代碼一句話:
RLock lock = redisson.getLock("myLock"); lock.tryLock(10, 30, TimeUnit.SECONDS);簡單、安全、高效,是生產(chǎn)環(huán)境的最佳實(shí)踐。
到此這篇關(guān)于如何使用Redis 實(shí)現(xiàn)分布式鎖(含自動(dòng)續(xù)期與安全釋放)的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis不同數(shù)據(jù)類型使用場(chǎng)景代碼實(shí)例
這篇文章主要介紹了Redis不同數(shù)據(jù)類型使用場(chǎng)景代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12
Redis 實(shí)現(xiàn)隊(duì)列原理的實(shí)例詳解
這篇文章主要介紹了Redis 實(shí)現(xiàn)隊(duì)列原理的實(shí)例詳解的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-09-09
Redis配置外網(wǎng)可訪問(redis遠(yuǎn)程連接不上)的方法
默認(rèn)情況下,當(dāng)我們?cè)诓渴鹆藃edis服務(wù)之后,redis本身默認(rèn)只允許本地訪問。Redis服務(wù)端只允許它所在服務(wù)器上的客戶端訪問,如果Redis服務(wù)端和Redis客戶端不在同一個(gè)機(jī)器上,就要進(jìn)行配置。2022-12-12
redis 億級(jí)數(shù)據(jù)讀取的實(shí)現(xiàn)
本文主要介紹了redis 億級(jí)數(shù)據(jù)讀取的實(shí)現(xiàn),億級(jí)數(shù)據(jù)規(guī)模下實(shí)現(xiàn)高效的數(shù)據(jù)讀取成為了許多企業(yè)和開發(fā)者面臨的重大挑戰(zhàn),下面就來介紹一下,感興趣的可以了解一下2024-08-08

