【Redis 】看門狗:分布式鎖的自動續期

在分布式系統的開發中,保證數據的一致性和避免并發沖突是至關重要的任務。Redis 作為一種廣泛使用的內存數據庫,提供了實現分布式鎖的有效手段。然而,傳統的 Redis 分布式鎖在設置了過期時間后,如果任務執行時間超過了鎖的有效期,就會出現鎖提前釋放,導致并發問題。為了解決這一難題,看門狗機制應運而生。

一、Redis 分布式鎖基礎回顧

Redis 分布式鎖通常基于 Redis 的單線程特性和原子操作來實現。最常見的方式是使用SET key value NX PX timeout命令。其中,NX表示只有當key不存在時才進行設置操作,保證了鎖的唯一性;PX timeout則設置了鎖的過期時間(單位為毫秒),防止因程序異常導致鎖無法釋放而產生死鎖。例如:

SET my_lock unique_value NX PX 30000

上述命令嘗試在 Redis 中設置一個名為my_lock的鎖,值為unique_value(通常是一個唯一標識,如線程 ID 或 UUID),并且設置鎖的過期時間為 30 秒。當一個客戶端成功執行該命令,就表示它獲取到了鎖。在任務完成后,客戶端需要通過DEL命令釋放鎖:

DEL my_lock

但這里存在一個問題,如果任務執行時間超過了 30 秒,鎖會自動過期并被 Redis 刪除,此時其他客戶端就有可能獲取到同一把鎖,導致并發安全問題。

二、看門狗機制原理

看門狗(Watchdog)機制是一種用于自動延長 Redis 分布式鎖有效期的解決方案。其核心思想是在持有鎖的線程或進程內,啟動一個后臺線程(或定時任務),定期檢查鎖是否仍然由當前持有者持有。如果是,則通過 Redis 的PEXPIRE命令延長鎖的過期時間,從而避免鎖在任務執行過程中提前過期。

具體來說,當一個客戶端成功獲取到 Redis 分布式鎖后,看門狗線程開始啟動。該線程會按照一定的時間間隔(通常是鎖過期時間的一部分,如 1/3 或 1/2)檢查鎖的狀態。例如,如果鎖的初始過期時間設置為 30 秒,看門狗線程可能每隔 10 秒檢查一次。在每次檢查時,它會執行類似于以下的操作:

# 檢查鎖是否仍由當前客戶端持有(假設鎖的值為unique_value)if redis.call('GET', 'my_lock') == 'unique_value' then# 延長鎖的過期時間redis.call('PEXPIRE','my_lock', 30000)end

上述邏輯可以通過 Redis 的 Lua 腳本來實現,以確保檢查和續期操作的原子性。這樣,只要持有鎖的任務還在執行,看門狗就會持續為鎖續期,直到任務完成并釋放鎖。

三、

Redisson 中的看門狗實現及示例代碼(Golang 版)?

在 Golang 生態中,雖然沒有 Java 中 Redisson 那樣原生的庫,但可以通過go-redis客戶端結合相關邏輯實現類似功能。以下是基于go-redis的示例代碼:?

(一)引入依賴?

首先需要安裝go-redis客戶端:

go get github.com/go-redis/redis/v8

(二)golang代碼示例

以下是一個使用 Redisson 實現分布式鎖并利用看門狗自動續期的 Java 示例代碼:

package mainimport (context"me"hub.com/go-redis/redis/v8"ithub.com/google/uuid"
)var ctx = context.Background()func main() {配置Redis客戶端b := redis.NewClient(&redis.Options{ddr:     "localhost:6379",assword: "", // 無密碼0,  // 默認DB分布式鎖Key := "my_distributed_lock"唯一標識ueValue := uuid.New().String()嘗試獲取鎖,設置過期時間30秒kSuccess, err := rdb.SetNX(ctx, lockKey, uniqueValue, 30*time.Second).Result()r != nil {t.Printf("獲取鎖失敗:%v\n", err)nlockSuccess {Println("獲取鎖失敗,鎖已被持有")nr func() {釋放鎖的Lua腳本leaseScript := `dis.call('GET', KEYS[1]) == ARGV[1] thenn redis.call('DEL', KEYS[1])ern 0d執行釋放鎖操作Eval(ctx, releaseScript, []string{lockKey}, uniqueValue)mt.Println("鎖已釋放")Println("獲取到鎖,開始執行業務邏輯...")看門狗協程自動續期Chan := make(chan struct{})go func() {ticker := time.NewTicker(10 * time.Second) // 每隔10秒檢查一次defer ticker.Stop()for {select {case <-ticker.C:// 檢查鎖是否仍由當前客戶端持有val, err := rdb.Get(ctx, lockKey).Result()if err != nil || val != uniqueValue {// 鎖已釋放或不屬于當前客戶端,停止續期return}// 續期30秒rdb.Expire(ctx, lockKey, 30*time.Second)fmt.Println("看門狗:鎖已續期")case <-stopChan:// 收到停止信號,退出return}}}()// 模擬業務邏輯執行(60秒)time.Sleep(60 * time.Second)fmt.Println("業務邏輯執行完畢")// 通知看門狗停止close(stopChan)// 關閉Redis客戶端rdb.Close()
}   stop  // 啟動    }() 

在上述代碼中:?

  1. 使用go-redis客戶端連接 Redis 服務器,并通過SetNX方法獲取分布式鎖,SetNX對應 Redis 的SET NX命令,第三個參數為過期時間。?
  1. 生成 UUID 作為鎖的唯一標識,確保釋放鎖時的安全性。?
  1. 獲取鎖成功后,啟動一個看門狗協程,通過定時器每隔 10 秒檢查一次鎖的狀態。如果鎖仍由當前客戶端持有(通過對比 value 值),則調用Expire方法延長鎖的過期時間。?
  1. 使用defer語句確保業務邏輯執行完畢后釋放鎖,釋放鎖通過 Lua 腳本實現,保證原子性。?
  1. 模擬 60 秒的業務邏輯執行,期間看門狗會自動續期,避免鎖提前過期。?

(三)優勢說明?

  1. 自動續期:通過 Golang 的協程和定時器實現看門狗功能,自動延長鎖的有效期。?
  1. 安全性:使用 UUID 作為唯一標識,結合 Lua 腳本釋放鎖,避免誤釋放其他客戶端的鎖。?
  1. 簡潔高效:基于go-redis客戶端,代碼簡潔,性能高效。

四、

手動實現看門狗機制示例(純 Golang 原生邏輯)?

如果不依賴第三方庫,也可以通過 Golang 的net/http包中的 Redis 客戶端相關邏輯手動實現,但實際開發中建議使用成熟的go-redis客戶端。以下是更貼近手動實現思想的示例:

package mainimport ("context""fmt""time""github.com/go-redis/redis/v8""github.com/google/uuid"
)var ctx = context.Background()// acquireLock 獲取分布式鎖
func acquireLock(rdb *redis.Client, lockKey, uniqueValue string, expireTime time.Duration) (bool, error) {return rdb.SetNX(ctx, lockKey, uniqueValue, expireTime).Result()
}// releaseLock 釋放分布式鎖
func releaseLock(rdb *redis.Client, lockKey, uniqueValue string) error {releaseScript := `if redis.call('GET', KEYS[1]) == ARGV[1] thenreturn redis.call('DEL', KEYS[1])elsereturn 0end`_, err := rdb.Eval(ctx, releaseScript, []string{lockKey}, uniqueValue).Result()return err
}// watchdog 看門狗協程,定期續期
func watchdog(rdb *redis.Client, lockKey, uniqueValue string, expireTime time.Duration, stopChan <-chan struct{}) {ticker := time.NewTicker(expireTime / 3) // 每隔過期時間的1/3檢查一次defer ticker.Stop()for {select {case <-ticker.C:val, err := rdb.Get(ctx, lockKey).Result()if err != nil || val != uniqueValue {return}// 續期rdb.Expire(ctx, lockKey, expireTime)fmt.Println("看門狗:鎖已續期")case <-stopChan:return}}
}func main() {rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379",})defer rdb.Close()lockKey := "my_distributed_lock"uniqueValue := uuid.New().String()expireTime := 30 * time.Second// 獲取鎖lockSuccess, err := acquireLock(rdb, lockKey, uniqueValue, expireTime)if err != nil || !lockSuccess {fmt.Println("獲取鎖失敗")return}defer releaseLock(rdb, lockKey, uniqueValue)defer fmt.Println("鎖已釋放")fmt.Println("獲取到鎖,開始執行業務邏輯...")// 啟動看門狗stopChan := make(chan struct{})go watchdog(rdb, lockKey, uniqueValue, expireTime, stopChan)// 模擬業務邏輯time.Sleep(60 * time.Second)fmt.Println("業務邏輯執行完畢")// 停止看門狗close(stopChan)
}

在這個示例中:

  1. acquire_lock函數使用redis_client.set方法嘗試獲取分布式鎖,nx=True表示只有當鎖不存在時才設置,ex=expire_time設置了鎖的過期時間。
  1. release_lock函數通過 Redis 的 Lua 腳本實現了安全的鎖釋放操作,只有當鎖的值與當前持有鎖的唯一標識相同時才刪除鎖。
  1. watchdog函數是看門狗線程的執行函數,它每隔expire_time / 3秒檢查一次鎖是否仍由當前線程持有,如果是,則調用redis_client.expire方法延長鎖的過期時間。
  1. 在main函數中,首先嘗試獲取鎖。如果獲取成功,啟動看門狗線程,然后模擬業務邏輯執行 60 秒。最后在業務完成后,釋放鎖。

五、總結與注意事項

看門狗機制為 Redis 分布式鎖的可靠性提供了重要保障,尤其適用于任務執行時間不確定或較長的場景。使用 Golang 實現時,需要注意以下幾點:?

  1. 協程管理:Golang 中通過協程實現看門狗,需確保協程能正常退出,避免泄漏。可通過channel傳遞退出信號。?
  1. 唯一標識:必須使用唯一標識(如 UUID)作為鎖的值,避免釋放鎖時誤刪其他客戶端的鎖。?
  1. 原子操作:檢查鎖狀態和續期操作需保證原子性,雖然 Golang 中通過分步操作實現,但實際通過短間隔和唯一標識降低了沖突風險,更嚴謹的方式是使用 Lua 腳本。?
  1. 異常處理:需處理 Redis 連接異常、網絡中斷等情況,可在看門狗中增加重試機制或錯誤告警。?
  1. 集群環境:在 Redis 集群或主從架構中,需考慮數據同步問題,必要時結合 Redlock 等算法提升可靠性。?

總之,Golang 的協程和定時器特性非常適合實現 Redis 看門狗機制,通過合理的設計可以高效解決分布式鎖的自動續期問題,保障分布式系統的并發安全。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/89653.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/89653.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/89653.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MYSQL--快照讀和當前讀及并發 UPDATE 的鎖阻塞

快照讀和當前讀在 MySQL 中&#xff0c;數據讀取方式主要分為 快照讀 和 當前讀&#xff0c;二者的核心區別在于是否依賴 MVCC&#xff08;多版本并發控制&#xff09;的歷史版本、是否加鎖&#xff0c;以及讀取的數據版本是否為最新。以下是詳細說明&#xff1a;一、快照讀&am…

css樣式中的選擇器和盒子模型

目錄 一、行內樣式二、內部樣式三、外部樣式四、結合選擇器五、屬性選擇器六、包含選擇器七、子選擇器八、兄弟選擇器九、選擇器組合十、偽元素選擇器十一、偽類選擇器十二、盒子模型 相關文章 學習標簽、屬性、選擇器和外部加樣式積累CSS樣式屬性&#xff1a;padding、marg…

關于基于lvgl庫做的注冊登錄功能的代碼步驟:

以下是完整的文件拆分和代碼存放說明&#xff0c;按功能模塊化劃分&#xff0c;方便工程管理&#xff1a;一、需要創建的文件清單 文件名 作用 類型 main.c 程序入口&#xff0c;初始化硬件和LVGL 源文件 ui.h 聲明界面相關函數 頭文件 ui.c 實現登錄、注冊、主頁面的UI 源文…

RAII機制以及在ROS的NodeHandler中的實現

好的&#xff0c;這是一個非常核心且優秀的設計問題。我們來分兩步詳細解析&#xff1a;先徹底搞懂什么是 RAII&#xff0c;然后再看 ros::NodeHandle 是如何巧妙地運用這一機制的。1. 什么是 RAII 機制&#xff1f; RAII 是 “Resource Acquisition Is Initialization” 的縮寫…

Linux LVS集群技術

LVS集群概述1、集群概念1.1、介紹集群是指多臺服務器集中在一起&#xff0c;實現同一業務&#xff0c;可以視為一臺計算機。多臺服務器組成的一組計算機&#xff0c;作為一個整體存在&#xff0c;向用戶提供一組網絡資源&#xff0c;這些單個的服務器就是集群的節點。特點&…

spring-ai-alibaba如何上傳文件并解析

問題引出 在我們日常使用大模型時&#xff0c;有一類典型的應用場景&#xff0c;就是將文件發送給大模型&#xff0c;然后由大模型進行解析&#xff0c;提煉總結等&#xff0c;這一類功能在官方app中較為常見&#xff0c;但是在很多模型的api中都不支持&#xff0c;那如何使用…

「雙容器嵌套布局法」:打造清晰層級的網頁架構設計

一、命名與核心概念 “雙容器嵌套布局法”&#xff0c;核心是通過兩層容器嵌套構建網頁結構&#xff1a;外層容器負責控制布局的“宏觀約束”&#xff08;如頁面最大寬度、背景色等&#xff09;&#xff0c;內層容器聚焦“微觀排版”&#xff08;內容居中、內邊距調整、紅色內容…

基于深度學習的自然語言處理:構建情感分析模型

前言 自然語言處理&#xff08;NLP&#xff09;是人工智能領域中一個非常活躍的研究方向&#xff0c;它致力于使計算機能夠理解和生成人類語言。情感分析&#xff08;Sentiment Analysis&#xff09;是NLP中的一個重要應用&#xff0c;其目標是從文本中識別和提取情感傾向&…

JWT原理及利用手法

JWT 原理 JSON Web Token (JWT) 是一種開放的行業標準&#xff0c;用于在系統之間以 JSON 對象的形式安全地傳輸信息。這些信息經過數字簽名&#xff0c;因此可以被驗證和信任。其常用于身份驗證、會話管理和訪問控制機制中傳遞用戶信息。 與傳統的會話令牌相比&#xff0c;JWT…

DeepSeek 助力 Vue3 開發:打造絲滑的日歷(Calendar),日歷_睡眠記錄日歷示例(CalendarView01_30)

前言&#xff1a;哈嘍&#xff0c;大家好&#xff0c;今天給大家分享一篇文章&#xff01;并提供具體代碼幫助大家深入理解&#xff0c;徹底掌握&#xff01;創作不易&#xff0c;如果能幫助到大家或者給大家一些靈感和啟發&#xff0c;歡迎收藏關注哦 &#x1f495; 目錄DeepS…

git的diff命令、Config和.gitignore文件

diff命令&#xff1a;比較git diff xxx&#xff1a;工作目錄 vs 暫存區&#xff08;比較現在修改之后的工作區和暫存區的內容&#xff09;git diff --cached xxx&#xff1a;暫存區 vs Git倉庫&#xff08;現在暫存區內容和最一開始提交的文件內容的比較&#xff09;git diff H…

Linux中的LVS集群技術

一、實驗環境&#xff08;RHEL 9&#xff09;1、NAT模式的實驗環境主機名IP地址網關網絡適配器功能角色client172.25.254.111/24&#xff08;NAT模式的接口&#xff09;172.25.254.2NAT模式客戶機lvs172.25.254.100/24&#xff08;NAT模式的接口&#xff09;192.168.0.100/24&a…

【數據結構】「隊列」(順序隊列、鏈式隊列、雙端隊列)

- 第 112篇 - Date: 2025 - 07 - 20 Author: 鄭龍浩&#xff08;仟墨&#xff09; 文章目錄隊列&#xff08;Queue&#xff09;1 基本介紹1.1 定義1.2 棧 與 隊列的區別1.3 重要術語2 基本操作3 順序隊列(循環版本)兩種版本兩種版本區別版本1.1 - rear指向隊尾后邊 且 無 size …

Java行為型模式---解釋器模式

解釋器模式基礎概念解釋器模式&#xff08;Interpreter Pattern&#xff09;是一種行為型設計模式&#xff0c;其核心思想是定義一個語言的文法表示&#xff0c;并定義一個解釋器&#xff0c;使用該解釋器來解釋語言中的句子。這種模式將語法解釋的責任分開&#xff0c;使得語法…

[spring6: PointcutAdvisor MethodInterceptor]-簡單介紹

Advice Advice 是 AOP 聯盟中所有增強&#xff08;通知&#xff09;類型的標記接口&#xff0c;表示可以被織入目標對象的橫切邏輯&#xff0c;例如前置通知、后置通知、異常通知、攔截器等。 package org.aopalliance.aop;public interface Advice {}BeforeAdvice 前置通知的標…

地圖定位與導航

定位 1.先申請地址權限(大致位置精準位置) module.json5文件 "requestPermissions": [{"name": "ohos.permission.INTERNET" },{"name": "ohos.permission.LOCATION","reason": "$string:app_name",&qu…

【數據結構】揭秘二叉樹與堆--用C語言實現堆

文章目錄1.樹1.1.樹的概念1.2.樹的結構1.3.樹的相關術語2.二叉樹2.1.二叉樹的概念2.2.特殊的二叉樹2.2.1.滿二叉樹2.2.2.完全二叉樹2.3.二叉樹的特性2.4.二叉樹的存儲結構2.4.1.順序結構2.4.2.鏈式結構3.堆3.1.堆的概念3.2.堆的實現3.2.1.堆結構的定義3.2.2.堆的初始化3.2.3.堆…

區間樹:多維數據的高效查詢

區間樹&#xff1a;多維數據的高效查詢 大家好&#xff0c;今天我們來探討一個在計算機科學中非常有趣且實用的數據結構——區間樹。想象一下&#xff0c;你是一位城市規劃師&#xff0c;需要快速找出某個區域內所有的醫院、學校或商場。或者你是一位游戲開發者&#xff0c;需要…

SQL 魔法:LEFT JOIN 與 MAX 的奇妙組合

一、引言 在數據庫操作的領域中&#xff0c;數據的關聯與聚合處理是核心任務之一。LEFT JOIN作為一種常用的連接方式&#xff0c;能夠將左表中的所有記錄與右表中滿足連接條件的記錄進行關聯&#xff0c;即便右表中沒有匹配的記錄&#xff0c;左表的記錄也會被保留&#xff0c;…

手寫tomcat

package com.qcby.annotation;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;Target(ElementType.TYPE)// 表示該注解只能用于類上 Retention(Retentio…