Redis分布式鎖的學習(八)

一、分布式鎖

1.1、分布式鎖是什么?

是一種在分布式系統中協調多個進程/服務對共享資源進行互斥訪問的機制;確保在任意時刻,只有一個客戶端可以訪問資源。
在這里插入圖片描述

1.2、為什么需要分布式鎖?

  • 解決多個服務/進程對同共享資源競爭,比如雙11,618購物節,多名用戶對同一個商品下單,導致庫存超賣問題。
  • 防止重復操作,比如用戶連續點擊導致重復下單

1.3、分布式鎖需要具備的條件和剛需有哪些?

  • 獨占性:任何時刻只能有且僅有一個線程持有
  • 高可用:
    • 若redis集群環境下,不能因為某一個節點掛了而出現獲取鎖和釋放鎖失敗的情況
    • 高并發請求下,依舊性能好使
  • 防死鎖:杜絕死鎖,必須有超時控制機制或撤銷操作,有個兜底終止跳出方案
  • 不亂搶:不能unlock別人的鎖,只能自己加鎖自己釋放,自己的鎖自己解
  • 重入性:同一個節點的同一個線程如果獲取鎖之后,它也可以再次獲取這個鎖

1.4、建立分布式鎖

# 第一種
setnx key value
EXPIRE KEY 60# 第二種
set key value [EX seconds][PX milliseconds][NX|XX]

使用setnx確保只有一個客戶端能成功設置鍵,通過EXPIRE設置過期時間,防止死鎖,但setnx + expire不安全,兩條命令非原子性。可以通過 Lua腳本 來實現分布式鎖。

-- 加鎖
if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 thenredis.call('PEXPIRE', KEYS[1], ARGV[2])return 1
end-- 釋放鎖
if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])
elsereturn 0
end

二、RedLock

官網地址:https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/

2.1、RedLock是什么?

ReadLock是一種算法,是一個更為規范的算法來使用Redis實現分布式鎖,實現了比普通單實例方法更安全的DLM(分布式鎖管理器)。

2.2、為什么使用RedLock?

在這里插入圖片描述

在這里插入圖片描述

導致問題:

    1. 主機宕機了,從機上位,變成新的master,用戶依舊可以建鎖成功,出現 一鎖被多建多用 ,違法安全規定
    1. 多個線程獲取到同一把鎖,可能會導致各種預期之外的情況發生,比如臟數據

2.3、RedLock算法設計理念

在這里插入圖片描述

大致方案如下:

假設有5個Redis主節點,不使用復制或任何其他隱式協調系統,為了獲得鎖客戶端執行以下操作:

步驟說明
1獲取當前時間,以毫秒為單位
2依次嘗試從5個實例,使用相同的key和隨機值(如UUID)獲取值,當向Redis請求獲取值時,客戶端應該設置一個超時時間,這個超時時間應該小于鎖的失效時間。這樣可以防止客戶端在試圖與一個宕機的Redis節點對話時,長時間處于阻塞狀態。如果一個實例不可用,客戶端應該盡快嘗試去另外一個Redis實例請求數據
3客戶端通過當前時間減去步驟1記錄的時間 = 獲取鎖使用的時間。當且僅當從大多數(N/2+1)的Redis節點都渠道鎖,并且獲取鎖使用的時間 < 鎖失效時間時,鎖才算獲取成功
4如果取到鎖,其真正有效時間 = 初始有效時間 - 獲取鎖使用時間
5如果由于某些原因未能獲得鎖(無法在至少N/2 + 1個Redis實例獲取鎖、或獲取鎖的時間超過了有效時間),客戶端應該在所有的Redis實例上進行解鎖

注意:

客戶端只有在滿足以下兩個條件時,才認為加鎖成功

    1. 客戶端從超過半數(>= N/2 + 1)的Redis實例上成功獲取了鎖
    1. 客戶端獲取鎖的總耗時沒有超過鎖的有效時間

2.4、容錯公式

N:最終部署機器數,X:容錯機器數

N = 2X + 1

為什么是奇數?

從成本上來考慮,用最少的機器,達到最多的產出效果

三、實際操作

3.1、手寫分布式鎖

  • 注意點:
    • lock關鍵:
      • 加鎖:在redis中,設置鍵,并設置過期時間
      • 自旋
      • 續期
    • unlock關鍵:不能unlock別人的鎖,只能自己加鎖自己釋放,自己的鎖自己解

3.2、利用redlock算法實現分布式鎖

使用開源庫redis-plus-plus: https://github.com/sewenew/redis-plus-plus

redlock代碼路徑:redis-plus-plus/src/sw/redis++/patterns/

類名作用
RedLockUtils工具類,提供ttl(計算時間差)和鎖ID
RedMutexTx基于Redis事務的分布式鎖實現
RedMutex作者推薦用戶直接操作的分布式鎖的類,根據配置選擇使用腳本(RedLockMutex)或事務(RedMutexTx
RedLockMutex針對單個Redis實例的分布式鎖,使用腳本實現
RedLockMutexVessel管理多個Redis實例的分布式鎖,使用腳本實現
RedMutexOptionsRedLock的配置選項,包括鎖的TTL、重試延遲、是否使用腳本
RedMutexImpl抽象基類
RedMutexImplTpl模板類,繼承自RedMutexImpl,根據模板參數(RedLockMutexRedMutexTx)實現具體操作
RedLock是一個模板類,用于封裝分布式鎖的核心操作,根據模板參數(RedLockMutexRedMutexTx)實現具體操作
LockWatcher用于監控鎖的生命周期
/**     RedLock類       **/
/* 作者不是很推薦使用,更為推薦使用RedMutex,RedLock只是RedMutex的簡單封裝.
*/
template <typename RedisInstance>
class RedLock {
public:RedLock(RedisInstance &mut, std::defer_lock_t) : _mut(mut), _lock_val(RedLockUtils::lock_id()) {}~RedLock() {if (owns_lock()) {unlock();}}// Try to acquire the lock for *ttl* milliseconds.// Returns how much time still left for the lock, i.e. lock validity time.bool try_lock(const std::chrono::milliseconds &ttl) {auto time_left = _mut.try_lock(_lock_val, ttl);if (time_left <= std::chrono::milliseconds(0)) {return false;}_release_tp = std::chrono::steady_clock::now() + time_left;return true;}......void unlock() {try {_mut.unlock(_lock_val);_release_tp = std::chrono::time_point<std::chrono::steady_clock>{};} catch (const Error &) {_release_tp = std::chrono::time_point<std::chrono::steady_clock>{};throw;}}bool owns_lock() const {if (ttl() <= std::chrono::milliseconds(0)) {return false;}return true;}std::chrono::milliseconds ttl() const {auto t = std::chrono::steady_clock::now();return std::chrono::duration_cast<std::chrono::milliseconds>(_release_tp - t);}private:RedisInstance &_mut;std::string _lock_val;// The time point that we must release the lock.std::chrono::time_point<std::chrono::steady_clock> _release_tp{};
};/**         使用方法        **/
// 使用Lua腳本版本
{RedLockMutex mtx({redis1, redis2, redis3}, "resource");RedLock<RedLockMutex> lock(mtx, std::defer_lock);auto validity_time = lock.try_lock(std::chrono::seconds(30));validity_time = lock.extend_lock(std::chrono::seconds(10));lock.unlock();
} // Redis事務版本
{RedMutex mtx({redis1, redis2, redis3}, "resource");RedLock<RedMutex> lock(mtx, std::defer_lock);auto validity_time = lock.try_lock(std::chrono::seconds(30));validity_time = lock.extend_lock(std::chrono::seconds(30));lock.unlock();
}
/**     RedMutex類      **/
class RedMutex {
public:RedMutex(std::shared_ptr<Redis> master,const std::string &resource,std::function<void (std::exception_ptr)> auto_extend_err_callback = nullptr,const RedMutexOptions &opts = {},const std::shared_ptr<LockWatcher> &watcher = nullptr) :RedMutex(std::initializer_list<std::shared_ptr<Redis>>{master},resource, std::move(auto_extend_err_callback), opts, watcher) {}RedMutex(std::initializer_list<std::shared_ptr<Redis>> masters,const std::string &resource,std::function<void (std::exception_ptr)> auto_extend_err_callback = nullptr,const RedMutexOptions &opts = {},const std::shared_ptr<LockWatcher> &watcher = nullptr) :RedMutex(masters.begin(), masters.end(),resource, std::move(auto_extend_err_callback), opts, watcher) {}template <typename Input>RedMutex(Input first, Input last,const std::string &resource,std::function<void (std::exception_ptr)> auto_extend_err_callback = nullptr,const RedMutexOptions &opts = {},const std::shared_ptr<LockWatcher> &watcher = nullptr) {if (opts.scripting) {      //根據配置選項,選擇腳本還是事務實現_mtx = std::make_shared<RedMutexImplTpl<RedLockMutex>>(first, last, resource,std::move(auto_extend_err_callback), opts, watcher);} else {_mtx = std::make_shared<RedMutexImplTpl<RedMutexTx>>(first, last, resource,std::move(auto_extend_err_callback), opts, watcher);}}...private:std::shared_ptr<RedMutexImpl> _mtx;
};/**         使用方法        **/
auto redis = std::make_shared<Redis>("tcp://127.0.0.1");auto redis1 = std::make_shared<Redis>("tcp://127.0.0.1:7000");
auto redis2 = std::make_shared<Redis>("tcp://127.0.0.1:7001");
auto redis3 = std::make_shared<Redis>("tcp://127.0.0.1:7002");try {{// 單個實例RedMutex mtx(redis, "resource");std::lock_guard<RedMutex> lock(mtx);}{// 多個實例RedMutex mtx({redis1, redis2, redis3}, "resource");std::lock_guard<RedMutex> lock(mtx);}{RedMutexOptions opts;opts.ttl = std::chrono::seconds(5);auto watcher = std::make_shared<LockWatcher>();RedMutex mtx({redis1, redis2, redis3}, "resource",[](std::exception_ptr err) {try {std::rethrow_exception(err);} catch (const Error &e) {}},opts, watcher);std::unique_lock<RedMutex> lock(mtx, std::defer_lock);lock.lock();lock.unlock();lock.try_lock();}
} catch (const Error &err) {
}

先創建多個Redis獨立的實例
先開啟多個redis

創建多線程模擬多個客戶端并發訪問,模擬多個客戶端并發操作。

/**     創建多線程模擬多個客戶端并發訪問      **/
std::unique_lock<RedMutex> lock(mtx, std::defer_lock);
auto client_thread = [&](string client_id, int task_count){try{// 嘗試獲取鎖(非阻塞版本)    // if (!lock.try_lock()) {//     cerr << "[" << client_id << "] Failed to acquire lock - operation skipped" << endl;//     return;// }lock.lock();cout << "[" << client_id << "] acquired lock\n";// 模擬臨界區操作for (int i = 1; i <= task_count; ++i) {process_order(client_id, i);}// 手動釋放鎖(析構時也會自動釋放)lock.unlock();cout << "[" << client_id << "] released lock\n";// 隨機延遲后執行下一個任務dis.param(uniform_int_distribution<>::param_type(50, 300));this_thread::sleep_for(chrono::milliseconds(dis(gen)));}catch(const exception& e) {cerr << "[" << client_id << "] Error: " << e.what() << endl;}catch(const Error& e) {cerr << "Redis error: " << e.what() << endl;}};// 4. 創建多線程模擬多個客戶端并發訪問vector<thread> clients;vector<string> client_ids = {"ClientA", "ClientB", "ClientC"};for (const auto& id : client_ids) {clients.emplace_back(client_thread, id, 5);}

注意: try_lock() 非阻塞版本,lock() 阻塞版本, 盡量別混合使用,否則會死鎖。
在這里插入圖片描述


正常運行結果:

在這里插入圖片描述

四、總結

4.1、什么是分布式鎖?

是一種在分布式系統中協調多個進程/服務對共享資源進行互斥訪問的機制;確保在任意時刻,只有一個客戶端可以訪問資源。

4.2、分布式鎖和常見的鎖有什么區別?

鎖類型作用范圍實現方式性能特點典型應用場景
分布式鎖跨進程,跨機器外部存儲系統(如Redis、Zookeeper等)網絡開銷大多服務共享資源訪問
互斥鎖單進程內-單機鎖原子操作低延遲,高效率多線程共享內存訪問
讀寫鎖單進程內-單機鎖計數器+條件變量讀操作并發性好讀多寫少的共享數據
自旋鎖單進程內-單機鎖CPU忙等待循環低延遲,但浪費CPU短時間占用資源場景

4.3、RedLock分布式鎖的數據存儲與高可用性分析

    1. RedLock算法,將每個節點看作是獨立的,即沒有主從關系,每個節點都可以獨立地獲取和釋放鎖,并且它們之間沒有任何數據同步。
    1. RedLock算法本身不存儲業務數據,它只負責管理分布式鎖的狀態。
    1. 部分節點宕機,只要滿足 N/2+1 節點可用,鎖服務仍然正常;宕機節點上的鎖會在TTL過期后自動清理。

4.4、RedLock適用場景

個人認為,它不適用于需要強一致性的場景,只是在某些時間段,并發量突發時,避免超賣這類問題,比如雙11,618等活動,常規時間段還是還原成集群部署模式,使用單機鎖,保證數據的一致性。

Code
0vice·GitHub

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

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

相關文章

spring的常用注解匯總

在 Spring 和 Spring Boot 框架中&#xff0c;有許多核心注解被廣泛應用。以下是常用的關鍵注解分類詳解&#xff1a;一、組件聲明與依賴注入注解作用示例Component通用組件聲明 (Bean 的泛化形式)Component public class ServiceImpl {...}Service標記服務層&#xff08;業務邏…

Claude4、GPT4、Kimi K2、Gemini2.5、DeepSeek R1、Code Llama等2025主流AI編程大模型多維度對比分析報告

2025主流AI編程大模型多維度對比分析報告引言&#xff1a;AI編程大模型的技術格局與選型挑戰一、核心模型概覽&#xff1a;技術定位與市場份額1.國際第一梯隊&#xff08;1&#xff09;Claude 4系列&#xff08;Anthropic&#xff09;&#xff08;2&#xff09;GPT-4.1&#xf…

Overleaf中下載.aux和.bbl文件

有些會議提交終稿的時候&#xff0c;可能會讓上傳.bbl和.aux文件&#xff0c;但是使用Overleaf下載下來的壓縮包中缺沒有這些文件在網上搜了一下都是用的舊版的Overleaf的教程&#xff0c;或者教程比較繁瑣&#xff0c;其實新版的Overleaf也可以直接下載 打開你的論文編譯好&am…

uniapp寫app做測試手機通知欄展示內容

uniapp寫app做測試手機通知欄展示內容 以下代碼&#xff1a;只是個簡單測試能不能給手機發送消息&#xff0c;能不能引導打開通知權限&#xff0c;能不能進行跳轉的功能, 增加 notify.js 以下文件 // 模擬本地通知功能 export function showNotification() {// 1. 檢查通知…

分布式云計算:未來計算架構的全新演進

隨著信息技術的不斷發展,尤其是云計算技術的飛速進步,企業和個人對計算資源的需求已經從傳統的單一數據中心向更為靈活、可擴展的分布式架構轉變。分布式云計算作為一種新興的云計算模型,旨在將計算資源和數據存儲分布在多個地理位置上,從而提供更加高效、安全和可靠的服務…

2025年海外短劇獨立站開發:H5+PC端雙平臺技術實踐與增長策略

引言在全球化內容消費浪潮下&#xff0c;海外短劇市場正經歷爆發式增長。據DataEye《2025H1海外微短劇行業數據報告》顯示&#xff0c;2025年海外短劇市場規模預計突破45億美元&#xff0c;其中東南亞、拉美等新興市場貢獻超30%增量。本文將以某頭部短劇平臺的雙平臺開發實踐為…

OpenAI發布ChatGPT Agent,AI智能體迎來關鍵變革

注&#xff1a;此文章內容均節選自充電了么創始人&#xff0c;CEO兼CTO陳敬雷老師的新書《GPT多模態大模型與AI Agent智能體》&#xff08;跟我一起學人工智能&#xff09;【陳敬雷編著】【清華大學出版社】 清華《GPT多模態大模型與AI Agent智能體》書籍配套視頻課程【陳敬雷…

企業級安全威脅檢測與響應(EDR/XDR)架構設計

在這個網絡威脅如洪水猛獸的時代&#xff0c;企業的安全防護不能再像守城門的老大爺一樣只會喊"什么人&#xff1f;口令&#xff01;"了。我們需要的是一套像FBI一樣具備全方位偵察能力的智能防護系統。 &#x1f4cb; 文章目錄 1. 什么是EDR/XDR&#xff1f;別被這…

Stream流-Java

Stream流的作用&#xff1a;結合了Lambda表達式&#xff0c;簡化集合&#xff0c;數組的操作Stream流的使用步驟&#xff1a;1. 先得到一條Stream流&#xff08;流水線&#xff09;&#xff0c;并把數據放上去獲取方式方法名說明單列集合default Stream<E> stream()Colle…

Leetcode 327. 區間和的個數

1.題目基本信息 1.1.題目描述 給你一個整數數組 nums 以及兩個整數 lower 和 upper 。求數組中&#xff0c;值位于范圍 [lower, upper] &#xff08;包含 lower 和 upper&#xff09;之內的 區間和的個數 。 區間和 S(i, j) 表示在 nums 中&#xff0c;位置從 i 到 j 的元素…

MinIO 版本管理實踐指南(附完整 Go 示例)

? 前言 在構建企業級對象存儲系統時,“對象的版本管理”是一個關鍵特性。MinIO 作為一款高性能、Kubernetes 原生的 S3 兼容對象存儲系統,也支持強大的版本控制功能。 本文將通過 Go 示例代碼 + 實操講解 的形式,手把手帶你掌握 MinIO 的版本控制能力,包括開啟版本控制、…

數組toString方法及類型檢測修復方案

在 JavaScript 中&#xff0c;數組的 toString() 方法被覆蓋&#xff08;重寫&#xff09;為返回數組元素的逗號分隔字符串&#xff0c;而不是原始的 [object Array] 類型標識。以下是詳細解釋和修復方案&#xff1a;問題原因Array.prototype.toString 被覆蓋數組繼承自 Object…

mysql索引底層B+樹

B樹勝出的關鍵特性&#xff1a;矮胖樹結構&#xff1a;3-4層高度即可存儲2000萬條記錄&#xff08;假設每頁存1000條&#xff09; 葉子鏈表&#xff1a;所有數據存儲在葉子節點&#xff0c;并通過雙向鏈表連接 非葉導航&#xff1a;非葉子節點僅存儲鍵值&#xff0c;不保存數據…

AI開放課堂:釘釘MCP開發實戰

我們正處在AI技術爆發的時代&#xff0c;也處于企業數字化蓬勃發展的時代。如何利用AI技術&#xff0c;突破模型自身知識的局限&#xff0c;安全、高效地與外部世界連接和交互&#xff0c;是當前所有AI開發者在企業數字化中面臨的問題之一。 MCP&#xff08;Model Context Prot…

DigitalOcean 一鍵模型部署,新增支持百度開源大模型ERNIE 4.5 21B

使用過DigitalOcean GPU Droplet 服務器的用戶應該對我們的一鍵模型部署功能不陌生。DigitalOcean 的一鍵模型部署 (1-Click Models) 功能是 DO 為開發者和企業提供的一種便捷方式&#xff0c;用于快速部署和運行預訓練的生成式 AI 模型&#xff0c;尤其是大型語言模型 (LLM)。…

【嵌入式面試】嵌入式筆試與面試寶典(offer必來)

&#x1f48c; 所屬專欄&#xff1a;【嵌入式面試】 &#x1f600; 作??者&#xff1a;蘭舟比特 &#x1f43e; &#x1f680; 個人簡介&#xff1a;熱愛開源系統與嵌入式技術&#xff0c;專注 Linux、網絡通信、編程技巧、面試總結與軟件工具分享&#xff0c;持續輸出實用干…

企業級數據分析創新實戰:基于表格交互與智能分析的雙引擎架構

引言&#xff1a;數字化轉型中數據協同困境與系統融合挑戰 在數字化轉型實踐中&#xff0c;企業普遍面臨數據系統與業務運營的協同困境&#xff0c;主要表現為數據處理平臺與核心業務流程的架構隔離、分析成果與決策閉環的價值斷層、以及雙重數據維護帶來的資源損耗。這種系統…

openbmc 日志系統繼續分析

1.說明 1.1 總體說明 本節是繼: https://blog.csdn.net/wit_yuan/article/details/147142407?spm=1011.2415.3001.5331 后的繼續分析的文檔。 該篇內容主要目的是分析整個openbmc的日志系統。 注意解讀文檔: https://github.com/openbmc/docs/blob/master/designs/event-l…

【JIRA小白如何使用它進行bug管理】

JIRA小白如何使用它進行bug管理 提示&#xff1a;入職一般來說&#xff0c;公司會提供賬號&#xff0c;不需要部署如何提bug&#xff1a; JIRA有兩種提交方式 在執行測試用例中在bug管理項目中新建提bug建議或者注意事項&#xff1a; 標題&#xff1a;執行完A之后&#xff0c;發…