redis分布式鎖 Redisson在電商平臺開發中的實際應用

目錄

概述

Redis分布式鎖的實現方式

1. 基于SETNX命令(String類型)

2. 使用SET命令的NX和EX參數(推薦方式)

3. 基于Lua腳本實現復雜邏輯

4. RedLock算法(多節點Redis實現)

Redisson的分布式鎖

Redisson的鎖類型

Redisson可重入鎖的實現

Redis其他實現分布式鎖的方式

1. 基于Sorted Set實現公平鎖

2. 基于List實現分布式鎖

總結

實際應用場景

場景一:庫存超賣控制

業務場景

分布式鎖解決方案

關鍵實現細節

實際開發注意事項

場景二:訂單防重復提交

業務場景

分布式鎖解決方案

關鍵實現細節

實際開發注意事項

場景三:促銷活動限流與防超賣

業務場景

分布式鎖解決方案

關鍵實現細節

實際開發注意事項

分布式鎖在電商中的最佳實踐


概述

分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。下面詳細介紹Redis分布式鎖的實現方式以及Redisson的鎖類型。

Redis分布式鎖的實現方式

1. 基于SETNX命令(String類型)

最基本的實現方式是使用Redis的SETNX(SET if Not eXists)命令:

# 使用Python和Redis-py庫實現簡單分布式鎖
import redis
import timedef acquire_lock(redis_client, lock_name, acquire_timeout=10, lock_timeout=10):end_time = time.time() + acquire_timeoutlock_value = str(time.time() + lock_timeout)  # 鎖的過期時間while time.time() < end_time:# 使用SETNX嘗試獲取鎖if redis_client.setnx(lock_name, lock_value):# 設置鎖的過期時間,防止死鎖redis_client.expire(lock_name, lock_timeout)return True# 檢查鎖是否過期current_value = redis_client.get(lock_name)if current_value and float(current_value) < time.time():# 鎖已過期,嘗試競爭并獲取鎖old_value = redis_client.getset(lock_name, lock_value)if old_value == current_value:redis_client.expire(lock_name, lock_timeout)return Truetime.sleep(0.1)  # 短暫休眠避免頻繁重試return Falsedef release_lock(redis_client, lock_name):redis_client.delete(lock_name)

這種方式的問題是:

  • 鎖的釋放缺乏原子性
  • 不支持可重入
  • 沒有鎖失效機制(需手動設置過期時間)
2. 使用SET命令的NX和EX參數(推薦方式)

Redis 2.6.12版本后,SET命令支持原子化操作:

# 使用SET命令的NX和EX參數實現分布式鎖
def acquire_lock(redis_client, lock_name, acquire_timeout=10, lock_timeout=10):end_time = time.time() + acquire_timeoutwhile time.time() < end_time:# 使用SET命令原子化操作:NX表示只有鍵不存在時才設置,EX設置過期時間result = redis_client.set(lock_name, "locked", nx=True, ex=lock_timeout)if result:return Truetime.sleep(0.1)return False

這種方式解決了原子性問題,但仍不支持可重入。

3. 基于Lua腳本實現復雜邏輯

使用Lua腳本可以實現更復雜的原子操作,例如鎖的釋放:

# 使用Lua腳本確保鎖釋放的原子性
RELEASE_SCRIPT = """
if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])
elsereturn 0
end
"""def release_lock(redis_client, lock_name, lock_value):return redis_client.eval(RELEASE_SCRIPT, 1, lock_name, lock_value)
4. RedLock算法(多節點Redis實現)

為了提高可靠性,Redis作者提出了RedLock算法,基于多個Redis節點:

# RedLock算法的簡化實現
def redlock_acquire(redis_clients, lock_name, acquire_timeout=10, lock_timeout=10):start_time = time.time()lock_value = str(uuid.uuid4())  # 唯一標識鎖acquired_locks = 0for client in redis_clients:if time.time() > start_time + acquire_timeout:breakif client.set(lock_name, lock_value, nx=True, px=lock_timeout):acquired_locks += 1# 需要獲得多數節點的鎖才算成功if acquired_locks >= (len(redis_clients) // 2 + 1):return lock_value# 獲取鎖失敗,釋放已獲得的鎖for client in redis_clients:client.eval(RELEASE_SCRIPT, 1, lock_name, lock_value)return False

Redisson的分布式鎖

Redisson是一個基于Redis的Java駐內存數據網格(In-Memory Data Grid),它提供了分布式鎖的實現。

Redisson的鎖類型

Redisson的分布式鎖是可重入鎖,但從鎖的性質上來說,它屬于悲觀鎖。因為:

  • 它在獲取鎖后會阻塞其他線程的訪問
  • 遵循"先獲取鎖再操作"的悲觀策略
Redisson可重入鎖的實現

Redisson的可重入鎖通過Hash結構實現:

// Redisson可重入鎖的內部實現(簡化版)
public class RedissonLock {// 鎖的名稱private String lockName;// 當前線程IDprivate String threadId;// 重入次數private int reentrantCount = 0;// 嘗試獲取鎖public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();// 使用Hash結構存儲鎖信息// HSET lockName threadId 1Long currentLockCount = redisClient.hset(lockName, threadId, 1);if (currentLockCount == 0) {// 鎖已存在,檢查是否是當前線程持有String currentValueStr = redisClient.hget(lockName, threadId);if (currentValueStr != null) {int count = Integer.parseInt(currentValueStr);// 鎖重入redisClient.hset(lockName, threadId, count + 1);reentrantCount = count + 1;return true;}return false;}// 設置鎖的過期時間redisClient.expire(lockName, leaseTime, unit);reentrantCount = 1;return true;}// 釋放鎖public void unlock() {String threadId = Thread.currentThread().getId();// 獲取當前鎖的重入次數String currentValueStr = redisClient.hget(lockName, threadId);if (currentValueStr == null) {throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "+ threadId + " thread-id: " + Thread.currentThread().getId());}int count = Integer.parseInt(currentValueStr);if (count == 1) {// 完全釋放鎖redisClient.hdel(lockName, threadId);return;}// 減少重入次數redisClient.hset(lockName, threadId, count - 1);reentrantCount = count - 1;}
}

Redis其他實現分布式鎖的方式

1. 基于Sorted Set實現公平鎖
# 使用Sorted Set實現公平鎖
def acquire_fair_lock(redis_client, lock_name, client_id, acquire_timeout=10):end_time = time.time() + acquire_timeoutwhile time.time() < end_time:# 使用當前時間戳作為分數timestamp = time.time()# ZADD命令添加成員,NX確保只有不存在時才添加result = redis_client.zadd(lock_name, {client_id: timestamp}, nx=True)if result:# 獲取鎖成功return True# 檢查自己是否是隊列中的第一個first_client = redis_client.zrange(lock_name, 0, 0)if first_client and first_client[0] == client_id:return Truetime.sleep(0.01)return False
2. 基于List實現分布式鎖
# 使用List實現分布式鎖
def acquire_lock_with_list(redis_client, lock_name, acquire_timeout=10):end_time = time.time() + acquire_timeoutwhile time.time() < end_time:# LPUSH返回列表的長度result = redis_client.lpush(lock_name, "locked")if result == 1:# 設置過期時間redis_client.expire(lock_name, 10)return Truetime.sleep(0.1)return False

總結

Redis實現分布式鎖的核心思想是利用其原子操作和單線程特性。不同的實現方式適用于不同的場景:

  • 簡單場景:使用SET命令的NX和EX參數
  • 復雜場景:使用Redisson等成熟框架
  • 高可靠性場景:使用RedLock算法

Redisson的分布式鎖屬于悲觀鎖范疇,同時支持可重入特性,是企業級應用中常用的分布式鎖實現方案。在電商平臺中,分布式鎖是保障數據一致性和業務正確性的關鍵技術。以下是三個典型場景及實際開發中的使用方法:

實際應用場景

場景一:庫存超賣控制

業務場景

用戶下單時需要扣減庫存,若多個用戶同時購買同一商品,可能導致庫存超賣(如庫存1件,卻被10個用戶同時下單成功)。

分布式鎖解決方案
# 使用Redis分布式鎖防止庫存超賣
def deduct_stock(product_id, quantity):lock_name = f"stock_lock:{product_id}"with redisson_client.getLock(lock_name):  # 獲取分布式鎖# 查詢當前庫存current_stock = redis_client.get(f"stock:{product_id}")if current_stock >= quantity:# 扣減庫存redis_client.decr(f"stock:{product_id}", quantity)return Truereturn False
關鍵實現細節
  1. 鎖粒度:使用商品ID作為鎖名,確保同一商品的庫存操作互斥
  2. 原子性:庫存查詢和扣減操作必須在鎖內完成
  3. 超時設置:設置合理的鎖超時時間(如30秒),防止死鎖
實際開發注意事項
  • 使用Redisson的可重入鎖,避免同一線程重復獲取鎖時阻塞
  • 結合Lua腳本進一步優化庫存扣減邏輯,確保原子性
  • 庫存預扣減機制:先扣減緩存庫存,異步同步到數據庫

場景二:訂單防重復提交

業務場景

用戶點擊提交訂單按鈕后,由于網絡延遲等原因可能重復提交,導致創建多個相同訂單。

分布式鎖解決方案
// 使用Redis分布式鎖防止訂單重復提交
public Response createOrder(OrderRequest request) {String orderToken = request.getOrderToken(); // 前端生成的唯一標識RLock lock = redissonClient.getLock("order:" + orderToken);try {// 嘗試獲取鎖,1秒后自動釋放boolean isLocked = lock.tryLock(100, 1000, TimeUnit.MILLISECONDS);if (!isLocked) {return Response.failure("請勿重復提交訂單");}// 處理訂單創建邏輯Order order = orderService.createOrder(request);return Response.success(order);} catch (InterruptedException e) {Thread.currentThread().interrupt();return Response.failure("系統繁忙,請稍后再試");} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}
}
關鍵實現細節
  1. 唯一標識:前端生成唯一訂單Token(如UUID),隨請求一起傳遞
  2. 短超時:設置較短的鎖超時時間(如1秒),防止用戶正常操作被長時間阻塞
  3. 冪等性:結合數據庫唯一索引,確保訂單號唯一性
實際開發注意事項
  • 前端防抖處理:按鈕點擊后禁用,防止用戶手動重復點擊
  • 鎖的粒度控制:按訂單Token加鎖,而非全局鎖
  • 異常處理:確保鎖在異常情況下仍能釋放

場景三:促銷活動限流與防超賣

業務場景

限時秒殺、優惠券領取等促銷活動中,大量并發請求可能導致系統崩潰或資源超發。

分布式鎖解決方案
# 使用Redis分布式鎖和計數器實現秒殺限流
def seckill(product_id, user_id):lock_name = f"seckill_lock:{product_id}"with redisson_client.getLock(lock_name):# 檢查活動是否結束if not is_activity_active(product_id):return "活動已結束"# 檢查用戶是否已參與if redis_client.sismember(f"seckill_users:{product_id}", user_id):return "每個用戶限參與一次"# 檢查庫存stock = redis_client.get(f"seckill_stock:{product_id}")if stock <= 0:return "已售罄"# 扣減庫存redis_client.decr(f"seckill_stock:{product_id}")# 記錄用戶參與redis_client.sadd(f"seckill_users:{product_id}", user_id)return "秒殺成功"
關鍵實現細節
  1. 雙重檢查:先檢查庫存再獲取鎖,減少鎖競爭
  2. 用戶限制:使用Set記錄已參與用戶,防止重復參與
  3. 庫存預熱:活動開始前將庫存加載到Redis
  4. 異步處理:訂單創建等耗時操作放入MQ異步處理
實際開發注意事項
  • 熱點商品處理:對熱門商品采用多節點RedLock或分段鎖
  • 熔斷機制:當并發過高時自動拒絕請求,保護系統
  • 降級策略:庫存為0時直接返回,不再加鎖

分布式鎖在電商中的最佳實踐

  1. 合理設置鎖超時時間:根據業務處理時間設置合理的超時值,避免死鎖
  2. 鎖粒度控制:盡量使用細粒度鎖(如按商品ID、訂單ID),避免全局鎖
  3. 異常處理:使用try-finally確保鎖釋放,或依賴Redisson的看門狗機制
  4. 性能優化
    • 減少鎖內操作時間
    • 使用讀寫鎖分離讀操作
    • 采用分段鎖處理熱點數據
  1. 監控與報警:監控鎖的持有時間、等待隊列長度等指標,及時發現異常

分布式鎖是電商系統的重要組成部分,但需結合業務場景選擇合適的實現方式,并注意性能與可靠性的平衡。

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

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

相關文章

joomla 使用nginx服務器只能打開首頁,其他頁面404的解決方案

最近一個客戶將Joomla4網站從原先的Apache服務器改為Nginx服務器&#xff0c;整個過程一切順利&#xff0c;但還原網站后發現只能打開首頁&#xff0c;其他頁面都是404。這個問題需要修改nginx的配置文件來解決。 偽靜態 在Apache中使用.htaccess來完成偽靜態路由的轉發&…

湖北理元理律師事務所企業債務紓困路徑:司法重整中的再生之道

中小企業債務危機常呈現“擔保鏈擴散”特征&#xff0c;單一債務可能引發企業崩盤。湖北理元理律師事務所通過預重整制度與企業債務重組技術&#xff0c;探索出“司法保護商業談判”的紓困模式。 一、企業債務風險處置四步法 緊急止血 申請司法保護&#xff1a;通過訴前調解…

利用DeepWiki高效閱讀項目源碼

想獲取更多高質量的Java技術文章&#xff1f;歡迎訪問Java技術小館官網&#xff0c;持續更新優質內容&#xff0c;助力技術成長 技術小館官網 DeepWiki 是一個強大的工具&#xff0c;專為程序員提供開源項目源碼的結構化文檔和 AI 驅動的問答功能&#xff0c;幫助快速理解復雜…

django rest_framework 前端網頁實現Token認證

rest_framework提供了幾種認證方式&#xff1a;Session、Token等。Session是最簡單的&#xff0c;幾乎不用寫任何代碼就可以是實現&#xff0c;Token方式其實也不復雜&#xff0c;網上的教程一大把&#xff0c;但是最后都是用Postman這類工具來實現API調用的&#xff0c;通過這…

面試題-函數類型的重載是啥意思

在 TypeScript 中&#xff0c;函數重載&#xff08;Function Overload&#xff09; 是指為同一個函數提供多個不同的調用簽名&#xff08;參數類型和返回值類型的組合&#xff09;&#xff0c;但函數體只有一個實現。這樣可以讓函數在不同的輸入下表現出不同的行為&#xff0c;…

磐基PaaS平臺MongoDB組件SSPL許可證風險與合規性分析(上)

#作者&#xff1a;任少近 文章目錄 1.背景與問題1.1.背景1.2.問題 3.SSPL條款解讀分析3.1.條款0&#xff1a;定義條款3.2.條款一&#xff1a;源代碼條款3.3.條款二&#xff1a;基本授權條款3.4.條款三&#xff1a;反規避保護條款3.5.條款四&#xff1a;逐字傳播條款3.6.條款五…

「Linux文件及目錄管理」輸入輸出重定向與管道

知識點解析 輸入/輸出重定向 標準輸入(stdin):默認從鍵盤讀取,文件描述符為0。標準輸出(stdout):默認輸出到終端,文件描述符為1。標準錯誤(stderr):默認輸出到終端,文件描述符為2。重定向符號: >:覆蓋輸出到文件(如command > file)。>>:追加輸出…

【Node】最佳Node.js后端開發模板推薦

Node.js 后端開發模板推薦 以下是幾個優秀的Node.js后端模板&#xff0c;它們都適合二次開發&#xff0c;各自有不同的特點和適用場景&#xff1a; 1. Express基礎模板 Express Generator (官方工具) 官方提供的快速搭建工具基礎MVC結構簡單易上手 npm install express-ge…

HALCON相機標定

相機標定簡介&#xff1a; 首先&#xff0c;相機會產生畸變&#xff0c;即實際圖像和拍攝圖像不一致&#xff0c;可以是凸性也可以是凹性形變&#xff0c;相機標定的過程就是將畸變圖像還原為原始圖像&#xff0c;并將圖像中的像素坐標轉換為世界坐標。 形如&#xff1a;相機內…

Solidity 入門教程(二):值類型全解 —— 布爾、整數、地址與字節數組

在上一章中&#xff0c;我們寫下了第一個 Solidity 合約并在 Remix 中成功運行。本章我們將深入了解 Solidity 中的幾種常用值類型&#xff08;Value Types&#xff09;&#xff0c;并通過示例代碼在 Remix 進行驗證。 一、Solidity 中的三種數據類型 在 Solidity 中&#xf…

16.大數據監控

0.說明 監控主要構成。 軟件版本。 1.exporter監控配置 1.1 node_exporter 啟動命令 nohup ./node_exporter &服務 創建文件 /etc/systemd/system/node_exporter.service&#xff1a; [Unit] DescriptionPrometheus Node Exporter Wantsnetwork-online.target Aft…

Tomcat項目本地部署(Servlet為例)

在Windows上部署 在idea中打開項目 首先我們需要準備一個Servlet項目&#xff0c;我之前的Servlet項目是用eclipse寫的&#xff0c;這種情況下如果用idea直接打開的話會出現左側目錄無法顯示的情況&#xff0c;這個時候我們就需要用別的方法打開 打開項目管理 如下圖&#…

安裝MySQL 5.7導入數據,修改密碼,創建賬號并授權

1. 準備工作 sudo yum update -y sudo yum install -y wget libaio numactl 2. 下載 MySQL 5.7 二進制包 wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz 3. 創建 MySQL 用戶和組 sudo groupadd mysql sudo useradd -r -g m…

基礎RAG實現,最佳入門選擇(八)

RAG重排序 RAG重排序技術以提高RAG系統中的檢索質量。重新排序充當初始檢索后的第二個過濾步驟&#xff0c;以確保最相關的內容用于響應生成。 重排序的關鍵概念 1.初始檢索&#xff1a;使用基本相似度搜索的第一遍&#xff08;準確度較低但速度更快&#xff09; 2.文檔評分…

Spring Boot 常用注解整理

Spring & Spring Boot 常用注解整理 現代的 Spring 與 Spring Boot 應用大量使用注解來簡化配置、管理組件和實現各種框架功能。本文系統整理了常用的 Spring/Spring Boot 注解&#xff0c;按照功能分類進行介紹。每個注解都會涵蓋其含義、提供來源、應用場景以及代碼示例…

深入理解 Cross-Entropy 損失函數:從原理到實踐

在深度學習中&#xff0c;損失函數是衡量模型性能的關鍵指標之一。對于多分類問題&#xff0c;Cross-Entropy 損失函數 是最常用的選擇之一。它不僅能夠有效衡量模型輸出與真實標簽之間的差異&#xff0c;還能通過梯度下降法指導模型的優化。本文將深入探討 Cross-Entropy 損失…

Vim-vimrc保存文件自動移除行末尾空格

Vim-vimrc保存文件自動移除行末尾空格 這段代碼通過設置 autocmd 和自定義函數&#xff0c;確保每次保存文件時都自動刪除文件中的行尾空格&#xff0c;同時不會影響光標和視圖的位置。它適用于所有文件類型&#xff0c;并且刪除操作不會引入錯誤&#xff0c;即使沒有行尾空格的…

Occt幾何內核快速入門

本文簡單介紹 Open Cascade Technology&#xff08;OCCT&#xff09;&#xff0c;提供了下載地址和文檔地址。通過OCCT的測試工具Draw&#xff0c;展示了OCCT的一些功能特性。介紹了OCCT集成開發的演示代碼&#xff0c;提供了源代碼下載地址和編譯過程文件。 一、簡介 Open C…

【Docker 08】Compose - 容器編排

&#x1f308; 一、Docker Compose 介紹 ? 1. Docker Compose 是什么 Docker Compose 是由 Docker 官方提供的一個用于定義和運行多容器應用的工具&#xff0c;它讓用戶可以通過一個 YAML 文件&#xff08;通常是 docker-compose.yml&#xff09;來配置應用所需要的服務&…

CentOS Stream 9平臺部署安裝MySQL8.4.1

1、在線下載安裝包 [rootlocalhost ~]# wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.4.1-1.el9.x86_64.rpm-bundle.tar 2、新建解壓文件夾 [rootlocalhost ~]#mkdir /root/sql 3、離線解壓安裝包安裝配置MySQL8 上傳安裝包到home下 [rootlocalhost ~]#c…