深入了解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-07Redis+IDEA實(shí)現(xiàn)單機(jī)鎖和分布式鎖的過程
這篇文章主要介紹了Redis+IDEA實(shí)現(xiàn)單機(jī)鎖和分布式鎖的過程,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07kubernetes環(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