Redis分布式鎖詳解:原理、實現與實戰案例

目錄

1. 什么是分布式鎖?

分布式鎖的核心要求

2. 基于Redis的分布式鎖實現方案

(1)基礎方案:SETNX + EXPIRE

(2)優化方案:SET NX PX(原子性加鎖)

(3)進階方案:RedLock(Redis官方推薦)

3. 實戰案例

案例1:防止重復下單

1. 加鎖階段

2. 業務邏輯階段

3. 釋放鎖階段

案例2:秒殺庫存扣減

案例3:分布式定時任務調度

4. 常見問題與解決方案

(1)鎖過期但業務未執行完?

(2)鎖被其他客戶端誤刪?

(3)Redis主從切換導致鎖丟失?

5. 總結


1. 什么是分布式鎖?

在分布式系統中,多個服務實例可能同時訪問共享資源(如數據庫、緩存等),為了避免并發問題(如超賣、重復提交等),我們需要一種跨JVM的鎖機制——分布式鎖

分布式鎖的核心要求

  1. 互斥性:同一時刻只有一個客戶端能持有鎖。

  2. 防死鎖:即使客戶端崩潰,鎖也能自動釋放。

  3. 高可用:鎖服務必須高可用(如Redis集群)。

  4. 可重入性(可選):同一個客戶端可以多次獲取同一把鎖。


2. 基于Redis的分布式鎖實現方案

Redis因其高性能和原子性操作(如SETNX),成為實現分布式鎖的常用方案。

(1)基礎方案:SETNX + EXPIRE

// 加鎖(錯誤示范,非原子性)
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order123", "1");
if (locked) {redisTemplate.expire("lock:order123", 10, TimeUnit.SECONDS);  // 設置過期時間// 執行業務邏輯...redisTemplate.delete("lock:order123");  // 釋放鎖
}

問題SETNXEXPIRE不是原子操作,如果加鎖后客戶端崩潰,鎖永遠不會釋放!


(2)優化方案:SET NX PX(原子性加鎖)

Redis 2.6+ 支持SET命令的NX(不存在才設置)和PX(毫秒級過期時間)參數:

// 正確方式:原子性加鎖 + 設置過期時間
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order123", "client1", 10, TimeUnit.SECONDS
);if (locked) {try {// 執行業務邏輯...} finally {// 釋放鎖(需判斷是否是自己加的鎖)if ("client1".equals(redisTemplate.opsForValue().get("lock:order123"))) {redisTemplate.delete("lock:order123");}}
}

關鍵改進

  • 使用SET NX PX保證原子性。

  • 設置唯一標識(如client1),避免誤刪其他客戶端的鎖。


(3)進階方案:RedLock(Redis官方推薦)

如果單點Redis不可靠,可以使用RedLock算法(需多個獨立Redis實例):

// RedLock示例(使用Redisson客戶端)
RLock lock1 = redissonClient1.getLock("lock:order123");
RLock lock2 = redissonClient2.getLock("lock:order123");
RLock lock3 = redissonClient3.getLock("lock:order123");RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {if (redLock.tryLock(10, 30, TimeUnit.SECONDS)) {  // 最多等待10秒,鎖30秒后自動過期// 執行業務邏輯...}
} finally {redLock.unlock();
}

適用場景:對一致性要求極高的場景(如金融交易)。


3. 實戰案例

案例1:防止重復下單

public String createOrder(String userId, String productId) {String lockKey = "lock:order:" + userId + ":" + productId;String clientId = UUID.randomUUID().toString();  // 唯一標識try {// 嘗試加鎖(等待5秒,鎖10秒后自動釋放)Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);if (!locked) {throw new RuntimeException("操作太頻繁,請稍后再試!");}// 檢查是否已下單if (orderService.hasOrder(userId, productId)) {throw new RuntimeException("請勿重復下單!");}// 創建訂單...return orderService.create(userId, productId);} finally {// 釋放鎖(需校驗clientId)if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.delete(lockKey);}}
}

這段代碼實現了一個防并發重復下單的訂單創建邏輯,核心是使用Redis分布式鎖來保證同一用戶對同一商品的訂單操作是串行化的。下面逐部分解析:


1. 加鎖階段
String lockKey = "lock:order:" + userId + ":" + productId;
String clientId = UUID.randomUUID().toString();
  • lockKey:鎖的鍵,格式為lock:order:{userId}:{productId},確保不同用戶或不同商品的鎖互不影響。

  • clientId:生成唯一標識(UUID),用于后續校驗鎖的歸屬,防止誤刪其他客戶端的鎖。

Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS
);
  • setIfAbsent:Redis的SETNX命令(原子性操作),如果lockKey不存在則加鎖,并設置:

    • clientId(鎖的持有者標識)。

    • 過期時間:10秒(防止死鎖)。

  • 返回值true表示加鎖成功,false表示鎖已被占用。

if (!locked) {throw new RuntimeException("操作太頻繁,請稍后再試!");
}
  • 如果加鎖失敗,直接拋出異常,提示用戶"操作太頻繁"(類似秒殺場景的限流)。


2. 業務邏輯階段
if (orderService.hasOrder(userId, productId)) {throw new RuntimeException("請勿重復下單!");
}
  • 檢查是否已下單:在鎖的保護下查詢訂單系統,防止重復下單(即使通過了前端校驗,仍需后端保證冪等性)。

return orderService.create(userId, productId);
  • 創建訂單:執行業務邏輯(如扣減庫存、生成訂單等)。


3. 釋放鎖階段
finally {if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.delete(lockKey);}
}
  • finally:確保鎖一定會被釋放,即使業務邏輯拋出異常。

  • 校驗clientId

    • 只釋放自己加的鎖(避免誤刪其他客戶端的鎖)。

    • 如果鎖已自動過期(10秒后),get操作返回null,不會執行刪除。

  • 原子性問題

    • 這里的getdelete是兩步操作,非原子性,極端情況下可能誤刪鎖(如鎖過期后,其他客戶端加鎖成功,但當前線程仍執行刪除)。

    • 改進方案:使用Lua腳本保證原子性(見下文補充)。


案例2:秒殺庫存扣減

public boolean seckill(Long productId, Long userId) {String lockKey = "lock:seckill:" + productId;String stockKey = "stock:" + productId;String clientId = UUID.randomUUID().toString();try {// 加鎖(防止超賣)Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 3,  // 鎖3秒(避免長時間阻塞)TimeUnit.SECONDS);if (!locked) {return false;  // 搶鎖失敗}// 檢查庫存Integer stock = Integer.valueOf(redisTemplate.opsForValue().get(stockKey));if (stock <= 0) {return false;  // 已售罄}// 扣減庫存(原子操作)redisTemplate.opsForValue().decrement(stockKey);// 生成訂單...orderService.createSeckillOrder(userId, productId);return true;} finally {// 釋放鎖if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.delete(lockKey);}}
}

案例3:分布式定時任務調度

多個服務實例同時運行定時任務時,需確保只有一個實例執行:

@Scheduled(cron = "0 */5 * * * ?")  // 每5分鐘執行一次
public void scheduledTask() {String lockKey = "lock:scheduled:report";String clientId = "server-" + System.getProperty("server.port");  // 用服務實例標識try {// 嘗試加鎖(鎖5分鐘)Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 5, TimeUnit.MINUTES);if (!locked) {return;  // 其他實例已執行}// 執行業務邏輯(生成報表...)reportService.generateDailyReport();} finally {// 釋放鎖if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.delete(lockKey);}}
}

4. 常見問題與解決方案

(1)鎖過期但業務未執行完?

  • 問題:鎖自動釋放后,其他客戶端可能獲取鎖,導致并發問題。

  • 解決方案:使用看門狗機制(如Redisson的lockWatchdogTimeout),自動續期鎖。

(2)鎖被其他客戶端誤刪?

  • 問題:客戶端A釋放了客戶端B的鎖。

  • 解決方案:加鎖時設置唯一標識(如UUID),釋放時校驗。

(3)Redis主從切換導致鎖丟失?

  • 問題:主節點加鎖后崩潰,從節點晉升但未同步鎖數據。

  • 解決方案:使用RedLock(多Redis實例)或ZooKeeper替代。


5. 總結

方案優點缺點適用場景
SETNX + EXPIRE簡單高效非原子性,可能死鎖低并發場景
SET NX PX原子操作單點故障一般分布式系統
RedLock高可用實現復雜金融級高一致性場景

最佳實踐

  • 優先使用SET NX PX?+ 唯一標識。

  • 高可用場景選擇Redisson的RLock或RedLock。

  • 結合業務設置合理的鎖超時時間。

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

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

相關文章

【能碳建設1】用AI+開源打造物聯網+能碳管理+交易SaaS系統的最短路徑實施指南

摘要 本指南為技術小白設計,目標是在最短時間內利用AI工具與開源系統,獨立完成一套物聯網平臺 + 能碳管理平臺 + 碳交易系統的SaaS最小可用版本(MVP),并可后續擴展。流程分為目標定義、技術選型、環境搭建、核心功能開發、SaaS化、多租戶、上線運維等環節,按天推進,每步…

CVPR中深度學習新范式:通用性、魯棒性與多模態的創新突破

來gongzhonghao【圖靈學術計算機論文輔導】&#xff0c;快速拿捏更多計算機SCI/CCF發文資訊&#xff5e;分享一個深度學習領域正在迅速升溫的前沿方向&#xff1a;通用性與魯棒性的深度神經網絡架構創新。隨著大模型在視覺、文本乃至多模態任務中的廣泛應用&#xff0c;體現出深…

Vue3 學習教程,從入門到精通,Vue 3 + Tailwind CSS 全面知識點與案例詳解(31)

Vue 3 Tailwind CSS 全面知識點與案例詳解一、Vue 3 核心語法知識點 1. Vue 3 基礎 創建 Vue 3 項目 使用 Vite 創建項目&#xff1a;npm create vuelatest # 選擇需要的特性&#xff08;如 TypeScript、Vue Router&#xff09;響應式數據 使用 ref 和 reactive&#xff1a;im…

Android中RecyclerView基本使用

一、RecyclerView 核心概念1. 基本組件關系2. 核心組件作用Adapter&#xff1a;數據與視圖的橋梁LayoutManager&#xff1a;控制布局方式&#xff08;線性/網格/瀑布流&#xff09;ViewHolder&#xff1a;緩存視圖組件ItemDecoration&#xff1a;添加分割線等裝飾ItemAnimator&…

A100用transformers推理gpt-oss

A100本地用transformers推理gpt-oss GPT-oss試用 gpt-oss有兩個原生配置是目前&#xff08;2025-8-8&#xff09;Ampere系列顯卡不支持的&#xff0c;分別是默認的MXFP4量化&#xff0c;以及Flash-attn V3。tranformers推理是比較慢的&#xff0c;可以用于研究模型本身&#x…

虛擬手機號工具使用

背景&#xff1a;注冊部分國外應用時需要國外手機號驗證&#xff0c;例如在注冊cursor時需要國外手機號 解決&#xff1a;使用虛擬手機號網頁進行驗證 https://temp-number.com/ 選擇自己需要的國家 選擇一個手機號 復制手機號到自己的app注冊頁面 并發送消息&#xff0c;然后…

【線程池】壓測確定線程池合適的參數

【線程池】壓測確定線程池合適的參數【一】案例說明【二】明確線程池核心參數及優化目標【1】線程池核心參數&#xff08;需壓測驗證的關鍵參數&#xff09;【2】優化目標【三】壓測前準備【1】環境搭建【2】線程池初始配置&#xff08;基于經驗值&#xff09;【3】壓測工具與監…

GPT OSS 雙模型上線,百度百舸全面支持快速部署

GPT OSS 是 OpenAI 推出的重量級開放模型&#xff0c;專為強推理能力、智能體任務及多樣化開發場景設計&#xff0c;標志著大模型在開放性與實用性上的重要突破。該系列包含兩款高性能模型&#xff1a;參數規模為 117B 的 GPT?OSS?120B 和 21B 的 GPT?OSS?20B。二者皆采用 …

C++高頻知識點(十七)

文章目錄81. 你對智能指針的了解82. 一元、二元仿函數的區別和使用背景一元仿函數二元仿函數83. 描述Linux下文件刪除的原理84. 什么是菱形繼承&#xff1f;有什么問題&#xff0c;怎么解決&#xff1f;解決菱形繼承問題85. IO多路復用是什么&#xff1f;selectpollepollselect…

如何優雅的使用進行參數校驗

在spring里面有一個注解 Validated可以在方法的入參里面這樣寫//方法 getActivityFlag(RequestBody Validated QueryActivityDto queryActivityDto) //參數詳情NotBlank(message "userId不能為空")private String userId;NotNull(message "storeId不能為空&q…

Java學習第一百一十部分——CI/CD

目錄 一、前言簡介 二、基本信息 三、優勢價值 四、核心流程 五、技術棧&#xff08;工具矩陣&#xff09; 六、最佳實踐 七、與DevOps關系 八、挑戰對策 九、使用建議 十、總結歸納 一、前言簡介 CI/CD 的本質是&#xff1a;通過自動化流水線&#xff0c;實現代碼從提…

關于 Cocoapods 使用

一、Podfile & .podspec 文件 1、Podfile 1.1. 什么是 pod 簡單來說&#xff0c;一個 pod 就是 xcode 里面的一個 dependency&#xff1a; Anyway&#xff0c;pod 就是第三方庫的意思。一個 pod 就是指一個第三方庫。 1.2. Podfile 有什么用 Podfile 可以理解為就是…

編程速遞:2025 年巴西 Embarcadero 會議,期待您的到來

每個英雄都有一段充滿奮斗的旅程&#xff0c;這段旅程引領他走向榮耀&#xff0c;而開發者英雄的旅程是2025年巴西Embarcadero大會的重點&#xff0c;以慶祝Delphi成立30周年。網站現已上線巴西Embarcadero在世界上最受期待的Delphi發展英雄會議召開前90天&#xff0c;推出了Em…

DevOps簡單教程應用

文章目錄概念一、環境準備二、gitlab配置三、.gitlab-ci.yml文件配置概念 Devops是一個概念&#xff0c;就是邊開發邊測試&#xff0c;能夠大大提升開發效率&#xff0c;本文使用pycharmgitlab實現一個簡單的DevOps流程 一、環境準備 需要一個測試環境&#xff0c;模擬部署&…

華為流程管理體系構建與落地 之—— 業務流程規劃【附全文閱讀】

這部分內容聚焦華為業務流程管理&#xff0c;詳細闡述了流程規劃、設計、運營、評估與優化的具體方法和內容&#xff0c;為企業構建和完善流程管理體系提供了全面的指導。流程規劃分類方法&#xff1a;介紹 POS、OES、OMS 等分類法&#xff0c;如 POS 法按規劃、運營、支持劃分…

Android 項目:畫圖白板APP開發(零)——功能介紹(筆鋒,分頁,縮放,多指,硬件加速等)

一、前言 本系列將全面的介紹一些有關Android 畫圖方面的知識。筆觸功能包括&#xff1a;顏色、粗細、透明度、筆鋒、橡皮&#xff1b;繪圖功能包括&#xff1a;分頁、縮放、多指、撤銷恢復、筆畫加速。別看功能這么多&#xff0c;簡單的部分會花較少篇幅介紹&#xff0c;著重會…

香橙派 RK3588 部署千問大模型 Qwen2-VL-2B 推理視頻

演示視頻 香橙派RK3588部署千問大模型Qwen2-VL-2B推理視頻一、場景假設 視頻輸入為一條網絡流&#xff0c;利用大模型對視頻中的圖像幀進行推理。由于大模型推理耗時長&#xff0c;無法對每幀都進行推理&#xff0c;因此采用跳幀推理的方式&#xff1a;當推理完一幀后&#xf…

排序概念以及插入排序

一、排序基本概念1.就地排序&#xff1a;使用恒定的額外空間來產生輸出就地排序只是在原數組空間進行排序處理&#xff0c;也就是輸入的數組和得到的數組是同一個2.內部排序和外部排序&#xff1a;待排序數據可以一次性載入到內存中為內部排序&#xff0c;反之數據量過大就是外…

Webpack 核心配置與最佳實踐指南

Webpack 是現代前端工程化的核心工具,理解其配置原理和優化技巧對開發效率至關重要。 一、Webpack 基礎架構 1、核心概念關系圖 2、核心概念詳解 概念 作用 示例配置 Entry 應用入口起點 entry: ‘./src/index.js’ Output 編譯結果輸出位置 output.path: path.resolve(__d…

GISBox私有云+SaaS:安全協同的地理智能平臺

一、概述 GISBox&#xff08;GIS 工具箱&#xff09;是一套能夠對GIS 影像、地形、傾斜攝影進行場景編輯、切片轉化、分發服務的 GIS 工具箱。同時&#xff0c;GISBox還支持私有云并一鍵開啟SaaS服務。 二、什么是私有云&#xff1f; 私有云服務是一種為企業或組織量身定制的…