深入了解Redis的看門狗機(jī)制
Redis鎖的延期機(jī)制,通常被稱為“看門狗”機(jī)制,是為了處理持有鎖的客戶端在執(zhí)行任務(wù)時(shí)發(fā)生崩潰或網(wǎng)絡(luò)分區(qū)等異常情況,導(dǎo)致鎖無法被釋放,從而避免死鎖的發(fā)生。
一、何為“看門狗”
看門狗機(jī)制的主要作用是自動(dòng)續(xù)期鎖,確保在節(jié)點(diǎn)完成任務(wù)之前,鎖不會(huì)過期。具體來說,當(dāng)一個(gè)節(jié)點(diǎn)獲取到鎖后,看門狗會(huì)定期檢查該鎖的過期時(shí)間,并在必要時(shí)延長(zhǎng)鎖的過期時(shí)間,確保節(jié)點(diǎn)可以順利完成任務(wù)。
二、分析
以下是Redisson看門狗機(jī)制的核心代碼片段:
// 初始化看門狗線程
private void startWatchdog() {
// 每隔10秒檢查一次鎖的狀態(tài)
long delay = 10 * 1000;
watchdogFuture = scheduler.scheduleWithFixedDelay(() -> {
try {
// 檢查當(dāng)前持有的鎖
checkAndExtendLocks();
} catch (Exception e) {
// 處理異常
handleWatchdogException(e);
}
}, delay, delay, TimeUnit.MILLISECONDS);
}
// 檢查并延長(zhǎng)鎖的過期時(shí)間
private void checkAndExtendLocks() {
for (RLock lock : locks) {
if (lock.isHeldByCurrentThread()) {
// 更新鎖的過期時(shí)間
lock.extendLeaseTime();
}
}
}
// 更新鎖的過期時(shí)間
private void extendLeaseTime() {
String script = "if redis.call('exists', KEYS[1]) == 1 then " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"else " +
"return 0; " +
"end";
// 執(zhí)行Redis腳本,更新鎖的過期時(shí)間
redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Collections.singletonList(lockKey), leaseTime);
}
在上述代碼中,startWatchdog方法啟動(dòng)了一個(gè)定時(shí)任務(wù),每隔10秒檢查一次當(dāng)前持有的鎖,并調(diào)用checkAndExtendLocks方法延長(zhǎng)鎖的過期時(shí)間。extendLeaseTime方法通過執(zhí)行Redis腳本來更新鎖的過期時(shí)間,確保鎖在任務(wù)完成之前不會(huì)過期。
tryLock方法的源碼解讀
Redisson中的tryLock方法是獲取鎖的核心方法之一,提供了非阻塞的嘗試獲取鎖的功能。以下是tryLock方法的核心實(shí)現(xiàn)及其源碼解讀。
tryLock方法的核心代碼
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long leaseTimeInMillis = unit.toMillis(leaseTime);
long currentTime = System.currentTimeMillis();
long lockExpireTime = currentTime + leaseTimeInMillis;
String lockValue = UUID.randomUUID().toString();
boolean acquired = tryAcquireLock(lockValue, leaseTimeInMillis);
if (!acquired && time > 0) {
long endTime = currentTime + time;
while (System.currentTimeMillis() < endTime) {
acquired = tryAcquireLock(lockValue, leaseTimeInMillis);
if (acquired) {
break;
}
Thread.sleep(10); // Retry interval
}
}
if (acquired) {
scheduleExpirationRenewal(lockValue, leaseTimeInMillis);
}
return acquired;
}
在這段代碼中,tryLock方法嘗試在指定的等待時(shí)間內(nèi)獲取鎖,并設(shè)置鎖的過期時(shí)間。方法參數(shù)包括:
waitTime:等待時(shí)間,即在超時(shí)前持續(xù)嘗試獲取鎖的時(shí)間。
leaseTime:鎖的過期時(shí)間。
unit:時(shí)間單位。
tryAcquireLock方法:tryAcquireLock方法嘗試實(shí)際獲取鎖,如果成功則返回true。
private boolean tryAcquireLock(String lockValue, long leaseTimeInMillis) {
String script = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 1; " +
"else " +
"return 0; " +
"end";
List<Object> keys = Collections.singletonList(lockKey);
List<Object> args = Arrays.asList(lockValue, leaseTimeInMillis);
Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), keys, args);
return result != null && result == 1;
}
該方法執(zhí)行Lua腳本:
使用setnx命令嘗試設(shè)置鎖的鍵值對(duì),如果成功則返回1。
使用pexpire命令設(shè)置鎖的過期時(shí)間。
scheduleExpirationRenewal方法
如果鎖獲取成功,scheduleExpirationRenewal方法會(huì)啟動(dòng)一個(gè)看門狗線程來自動(dòng)延長(zhǎng)鎖的過期時(shí)間。
private void scheduleExpirationRenewal(String lockValue, long leaseTimeInMillis) {
long delay = leaseTimeInMillis / 3;
scheduler.scheduleWithFixedDelay(() -> {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"end";
List<Object> keys = Collections.singletonList(lockKey);
List<Object> args = Arrays.asList(lockValue, leaseTimeInMillis);
redisTemplate.execute(new DefaultRedisScript<>(script, Void.class), keys, args);
}, delay, delay, TimeUnit.MILLISECONDS);
}
這個(gè)方法啟動(dòng)一個(gè)定時(shí)任務(wù),每隔leaseTimeInMillis / 3的時(shí)間間隔,檢查鎖是否仍然由當(dāng)前線程持有,如果是,則延長(zhǎng)其過期時(shí)間。
關(guān)鍵點(diǎn)總結(jié)tryLock方法提供了非阻塞的嘗試獲取鎖的功能,允許在指定的時(shí)間內(nèi)多次嘗試獲取鎖。
tryAcquireLock方法執(zhí)行Lua腳本,使用Redis命令setnx和pexpire來實(shí)現(xiàn)鎖的獲取和過期時(shí)間設(shè)置。
scheduleExpirationRenewal方法啟動(dòng)一個(gè)定時(shí)任務(wù),通過Lua腳本自動(dòng)延長(zhǎng)鎖的過期時(shí)間,以防止鎖在任務(wù)完成前過期。
通過上述代碼和解析,我們可以更清楚地理解Redisson中tryLock方法的工作原理以及其在分布式鎖管理中的作用。
看門狗機(jī)制的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
自動(dòng)續(xù)期:看門狗機(jī)制可以自動(dòng)續(xù)期鎖,確保任務(wù)在完成之前鎖不會(huì)過期。
可靠性高:通過定期檢查鎖的狀態(tài),看門狗機(jī)制可以確保鎖的持有狀態(tài),從而提高系統(tǒng)的可靠性。
缺點(diǎn):
資源消耗:看門狗機(jī)制需要后臺(tái)線程定期檢查鎖的狀態(tài),這會(huì)消耗一定的系統(tǒng)資源。
復(fù)雜性增加:看門狗機(jī)制的引入增加了系統(tǒng)的復(fù)雜性,可能需要額外的調(diào)試和維護(hù)工作。
看門狗機(jī)制的優(yōu)化
在使用Redisson的看門狗機(jī)制時(shí),針對(duì)具體的應(yīng)用場(chǎng)景和系統(tǒng)需求,可以進(jìn)行以下優(yōu)化: 合理設(shè)置檢查頻率:根據(jù)任務(wù)的執(zhí)行時(shí)間和系統(tǒng)的負(fù)載情況,合理設(shè)置看門狗線程的檢查頻率,既保證鎖的持有狀態(tài),又減少系統(tǒng)資源的消耗。 優(yōu)化Redis腳本:使用高效的Redis腳本來更新鎖的過期時(shí)間,減少Redis服務(wù)器的負(fù)載。 監(jiān)控和報(bào)警:建立完善的監(jiān)控和報(bào)警機(jī)制,及時(shí)發(fā)現(xiàn)和處理看門狗機(jī)制中的異常情況,確保系統(tǒng)的穩(wěn)定性。
三、案例實(shí)踐
案例1:訂單處理系統(tǒng)中的看門狗機(jī)制優(yōu)化
場(chǎng)景描述
在一個(gè)大型電商平臺(tái)的訂單處理系統(tǒng)中,訂單處理可能需要較長(zhǎng)時(shí)間。為了確保分布式鎖在處理過程中不會(huì)過期,系統(tǒng)啟用了Redisson的看門狗機(jī)制。然而,由于系統(tǒng)負(fù)載較高,看門狗線程的頻繁檢查導(dǎo)致了系統(tǒng)資源消耗問題。
解決方案
合理設(shè)置檢查頻率:通過調(diào)整Redisson配置,降低看門狗線程的檢查頻率,以減少系統(tǒng)資源消耗。
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setWatchdogTimeout(30000); // 將看門狗超時(shí)時(shí)間設(shè)置為30秒
RedissonClient redissonClient = Redisson.create(config);
2.優(yōu)化Redis腳本:使用Lua腳本來更新鎖的過期時(shí)間,減少Redis服務(wù)器的負(fù)載。
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('expire', KEYS[1], ARGV[2]) " +
"else return 0 end";
redisClient.eval(script, Collections.singletonList("myLock"), Arrays.asList("lockValue", "30"));
3.監(jiān)控和報(bào)警:建立監(jiān)控和報(bào)警機(jī)制,及時(shí)發(fā)現(xiàn)和處理看門狗機(jī)制中的異常情況。
// 示例:使用Prometheus進(jìn)行監(jiān)控
@Autowired
private MeterRegistry meterRegistry;
public void monitorWatchdog() {
meterRegistry.gauge("redisson_watchdog_status", redissonClient.getLock("myLock").isLocked() ? 1 : 0);
}
案例2:數(shù)據(jù)處理任務(wù)中的看門狗機(jī)制優(yōu)化
場(chǎng)景描述
在一個(gè)數(shù)據(jù)處理系統(tǒng)中,每個(gè)任務(wù)可能需要幾分鐘甚至更長(zhǎng)的時(shí)間才能完成。如果看門狗線程因故停止工作,可能導(dǎo)致鎖過期,導(dǎo)致數(shù)據(jù)不一致問題。
解決方案
引入備用線程:增加備用線程來監(jiān)控看門狗線程的狀態(tài),如果發(fā)現(xiàn)看門狗線程停止工作,立即啟動(dòng)備用線程進(jìn)行處理。
public class WatchdogBackup implements Runnable {
private final RedissonClient redissonClient;
public WatchdogBackup(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
@Override
public void run() {
RLock lock = redissonClient.getLock("myLock");
while (true) {
if (!lock.isLocked()) {
System.out.println("Watchdog stopped, acquiring lock...");
lock.lock();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public void startBackupWatchdog(RedissonClient redissonClient) {
Thread backupThread = new Thread(new WatchdogBackup(redissonClient));
backupThread.setDaemon(true);
backupThread.start();
}
2.冗余機(jī)制:設(shè)置多個(gè)看門狗線程,以提高系統(tǒng)的可靠性。
public void startRedundantWatchdogs(RedissonClient redissonClient) {
for (int i = 0; i < 3; i++) {
Thread watchdogThread = new Thread(new WatchdogBackup(redissonClient));
watchdogThread.setDaemon(true);
watchdogThread.start();
}
}
通過這些優(yōu)化措施,我們可以有效地提高看門狗機(jī)制的可靠性和效率,確保在長(zhǎng)時(shí)間任務(wù)執(zhí)行過程中鎖不會(huì)過期,從而避免數(shù)據(jù)不一致和系統(tǒng)資源消耗問題。
到此這篇關(guān)于深入了解Redis的看門狗機(jī)制的文章就介紹到這了,更多相關(guān)Redis 看門狗機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Ubuntu系統(tǒng)中Redis的安裝步驟及服務(wù)配置詳解
本文主要記錄了Ubuntu服務(wù)器中Redis服務(wù)的安裝使用,包括apt安裝和解壓縮編譯安裝兩種方式,并對(duì)安裝過程中可能出現(xiàn)的問題、解決方案進(jìn)行說明,以及在手動(dòng)安裝時(shí),服務(wù)器如何添加自定義服務(wù)的問題,需要的朋友可以參考下2024-12-12
使用Redis獲取數(shù)據(jù)轉(zhuǎn)json,解決動(dòng)態(tài)泛型傳參的問題
這篇文章主要介紹了使用Redis獲取數(shù)據(jù)轉(zhuǎn)json,解決動(dòng)態(tài)泛型傳參的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07
Redis+IDEA實(shí)現(xiàn)單機(jī)鎖和分布式鎖的過程
這篇文章主要介紹了Redis+IDEA實(shí)現(xiàn)單機(jī)鎖和分布式鎖的過程,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫的方法
這篇文章主要介紹了kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
解讀redis?slaveof命令執(zhí)行后為什么需要清庫重新同步
這篇文章主要介紹了redis?slaveof命令執(zhí)行后為什么需要清庫重新同步,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
高并發(fā)場(chǎng)景分析之redis+lua防重校驗(yàn)
這篇文章主要介紹了高并發(fā)場(chǎng)景分析之redis+lua防重校驗(yàn),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07

