基于Redis分布鎖+事務補償解決數據不一致性問題

基于Redis的分布式設備庫存服務設計與實現

概述

本文介紹一個基于Redis實現的分布式設備庫存服務方案,通過分布式鎖、重試機制和事務補償等關鍵技術,保證在并發場景下庫存操作的原子性和一致性。該方案適用于物聯網設備管理、分布式資源調度等場景。

代碼實現


import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;// 模擬設備庫存服務
public class DeviceInventoryService {private static final Logger logger = LoggerFactory.getLogger(DeviceInventoryService.class);private final Map<String, Integer> inventoryMap = new HashMap<>();private static final int MAX_RETRIES = 3;private static final int LOCK_EXPIRE_TIME = 10; // 鎖的過期時間,單位:秒private final Jedis jedis;public DeviceInventoryService(Jedis jedis) {this.jedis = jedis;}// 初始化庫存public void initializeInventory(String deviceId, int quantity) {inventoryMap.put(deviceId, quantity);logger.info("設備 {} 初始化庫存為 {}", deviceId, quantity);}// 嘗試獲取分布式鎖private boolean tryLock(String lockKey) {SetParams setParams = SetParams.setParams().nx().ex(LOCK_EXPIRE_TIME);String result = jedis.set(lockKey, "locked", setParams);return "OK".equals(result);}// 釋放分布式鎖private void releaseLock(String lockKey) {jedis.del(lockKey);}// 定時更新庫存public boolean updateInventory(String deviceId, int updateQuantity) {String lockKey = "inventory_lock:" + deviceId;int retries = 0;//重試次數while (retries < MAX_RETRIES) {if (tryLock(lockKey)) {try {return doUpdateInventory(deviceId, updateQuantity);} catch (Exception e) {logger.error("設備 {} 庫存更新失敗,重試第 {} 次", deviceId, retries + 1, e);} finally {releaseLock(lockKey);}}retries++;try {Thread.sleep(100); // 等待一段時間后重試} catch (InterruptedException e) {Thread.currentThread().interrupt();}}logger.error("設備 {} 庫存更新失敗,達到最大重試次數", deviceId);return false;}// 實際執行庫存更新操作private boolean doUpdateInventory(String deviceId, int updateQuantity) {int oldQuantity = inventoryMap.getOrDefault(deviceId, 0);try {// 記錄操作日志logger.info("設備 {} 開始更新庫存,更新前庫存: {}", deviceId, oldQuantity);// 模擬更新操作int newQuantity = oldQuantity + updateQuantity;if (newQuantity < 0) {throw new IllegalArgumentException("庫存不能為負數");}inventoryMap.put(deviceId, newQuantity);logger.info("設備 {} 庫存更新成功,當前庫存: {}", deviceId, newQuantity);return true;} catch (Exception e) {logger.error("設備 {} 庫存更新失敗: {}", deviceId, e.getMessage());// 進行事務補償compensateInventory(deviceId, oldQuantity);return false;}}// 事務補償private void compensateInventory(String deviceId, int oldQuantity) {inventoryMap.put(deviceId, oldQuantity);logger.info("設備 {} 庫存已恢復到更新前的狀態,當前庫存: {}", deviceId, oldQuantity);}// 模擬定時任務public static void main(String[] args) {try (Jedis jedis = new Jedis("localhost", 6379)) {DeviceInventoryService service = new DeviceInventoryService(jedis);service.initializeInventory("device001", 10);// 模擬定時更新庫存service.updateInventory("device001", 5);service.updateInventory("device001", -20); // 模擬更新失敗}}}

核心設計

分布式鎖機制

private boolean tryLock(String lockKey) {SetParams setParams = SetParams.setParams().nx().ex(LOCK_EXPIRE_TIME);String result = jedis.set(lockKey, "locked", setParams);return "OK".equals(result);
}
  • 使用Redis的set nx ex命令實現原子性加鎖
  • 將鎖的顆粒度設置到了設備上(根據實際業務設置)
  • 設置10秒過期時間,防止死鎖(根據實際業務設置過期時間)

重試機制

		int retries = 0;//重試次數while (retries < MAX_RETRIES) {if (tryLock(lockKey)) {try {return doUpdateInventory(deviceId, updateQuantity);} catch (Exception e) {logger.error("設備 {} 庫存更新失敗,重試第 {} 次", deviceId, retries + 1, e);} finally {releaseLock(lockKey);}}retries++;try {Thread.sleep(100); // 等待一段時間后重試} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
  • 最大重試次數三次(MAX_RETRIES)
  • 如果沒有獲取到鎖則等待重試,超過重試次數則終止

補償機制

private void compensateInventory(String deviceId, int oldQuantity) {inventoryMap.put(deviceId, oldQuantity);logger.info("設備 {} 庫存已恢復到更新前的狀態,當前庫存: {}", deviceId, oldQuantity);
}
  • 在doUpdateInventory捕獲異常后自動回滾
  • 基于版本號/快照的恢復機制
  • 保證最終數據一致性

關鍵代碼解析

public boolean updateInventory(String deviceId, int updateQuantity) {String lockKey = "inventory_lock:" + deviceId;int retries = 0;while (retries < MAX_RETRIES) {if (tryLock(lockKey)) {try {return doUpdateInventory(deviceId, updateQuantity);} finally {releaseLock(lockKey);}}// ...重試邏輯...}return false;
}
  • 獲取設備級別的分布式鎖
  • 執行庫存更新操作
  • 無論成功失敗都釋放鎖(finally保證)
  • 達到重試上限后返回失敗

核心操作方法

private boolean doUpdateInventory(String deviceId, int updateQuantity) {int oldQuantity = inventoryMap.getOrDefault(deviceId, 0);int newQuantity = oldQuantity + updateQuantity;if (newQuantity < 0) {throw new IllegalArgumentException("庫存不能為負數");}inventoryMap.put(deviceId, newQuantity);return true;
}
  • 前置校驗:庫存不能為負數
  • 原子性操作:庫存增減計算
  • 事務性更新:先計算后寫入

使用示例

初始化與測試

public static void main(String[] args) {try (Jedis jedis = new Jedis("localhost", 6379)) {DeviceInventoryService service = new DeviceInventoryService(jedis);service.initializeInventory("device001", 10);service.updateInventory("device001", 5);  // 成功:庫存15service.updateInventory("device001", -20); // 失敗:觸發補償}
}

預期輸出

INFO - 設備 device001 初始化庫存為 10
INFO - 設備 device001 開始更新庫存,更新前庫存: 10
INFO - 設備 device001 庫存更新成功,當前庫存: 15
INFO - 設備 device001 開始更新庫存,更新前庫存: 15
ERROR - 設備 device001 庫存更新失敗: 庫存不能為負數
INFO - 設備 device001 庫存已恢復到更新前的狀態,當前庫存: 15

擴展思考

優化方向

  1. Redis集群支持:當前為單節點Redis,可升級為Redis Cluster
  2. 鎖續期機制:添加看門狗線程自動續期鎖
  3. 庫存持久化:結合數據庫實現庫存持久化存儲
  4. 監控體系:添加Prometheus監控指標

注意事項

  1. 網絡分區場景下可能出現鎖狀態不一致
  2. 庫存更新操作應保持冪等性
  3. Redis連接需要配置合理的超時參數
  4. 生產環境建議使用Lua腳本保證原子性

通過本文實現的庫存服務,在保證線程安全的基礎上,能夠有效應對分布式環境下的資源競爭問題。實際部署時建議結合具體業務場景進行壓力測試和參數調優。

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

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

相關文章

RK3568筆記八十: Linux 小智AI環境搭建

若該文為原創文章&#xff0c;轉載請注明原文出處。 最近小智AI火了&#xff0c;韋老師出了 Linux 小智 AI 聊天機器人 版本&#xff0c;想移植到 RK3568上&#xff0c; 由于和韋老師硬件不同&#xff0c;所以需要交叉編譯一些庫&#xff0c;為后續移植做準備。 一、環境 1、…

C# SerialPort 使用詳解

總目錄 前言 在工業控制、物聯網、嵌入式開發等領域&#xff0c;串口通信&#xff08;Serial Port Communication&#xff09;是連接串行設備&#xff08;如條碼掃描器、GPS接收器等&#xff09;與計算機的重要手段。C# 提供了內置的 SerialPort 類&#xff0c;簡化了串口開發…

3D點云的深度學習網絡分類(按照作用分類)

1. 3D目標檢測&#xff08;Object Detection&#xff09; 用于在點云中識別和定位目標&#xff0c;輸出3D邊界框&#xff08;Bounding Box&#xff09;。 &#x1f539; 方法類別&#xff1a; 單階段&#xff08;Single-stage&#xff09;&#xff1a;直接預測3D目標位置&am…

LabVIEW 與 PLC 通訊的常見方式

在工業自動化和數據采集系統中&#xff0c;PLC&#xff08;可編程邏輯控制器&#xff09; 廣泛用于控制和監測各種設備&#xff0c;而 LabVIEW 作為強大的圖形化編程工具&#xff0c;常用于上位機數據處理和可視化。為了實現 LabVIEW 與 PLC 的高效通訊&#xff0c;常見的方法包…

2025 polarctf春季個人挑戰賽web方向wp

來個彈窗 先用最基礎的xss彈窗試一下 <script>alert("xss")</script>沒有內容&#xff0c;猜測過濾了script&#xff0c;雙寫繞過一下 <scrscriptipt>alert("xss")</scscriptript>background 查看網頁源代碼 查看一下js文件 類…

【Ai】--- 可視化 DeepSeek-r1 接入 Open WebUI(超詳細)

在編程的藝術世界里,代碼和靈感需要尋找到最佳的交融點,才能打造出令人為之驚嘆的作品。而在這座秋知葉i博客的殿堂里,我們將共同追尋這種完美結合,為未來的世界留下屬于我們的獨特印記。【Ai】--- 可視化 DeepSeek-r1 接入 Open WebUI(超詳細) 開發環境一、前情提要:你…

7.1-7.2考研408數據結構查找算法核心知識點深度解析

考研408數據結構查找算法核心知識點深度解析 一、查找基本概念 1.1 核心定義與易錯點 查找表與關鍵字 易錯點:混淆靜態查找表(僅查詢)與動態查找表(含插入/刪除操作)的應用場景。例如哈希表屬于動態查找結構,而分塊查找適用于靜態數據。難點:理解平均查找長度(ASL)的…

Redis--redis客戶端

目錄 一、引言 二、數據庫管理命令 三、redis客戶端 四、Java客戶端使用Redis 五、相關命令使用 1.get&#xff0c;set 2.exists&#xff0c;del 3.keys 4.expire&#xff0c;ttl 六、總結 一、引言 在之前學了redis相關類型命令之后&#xff0c;本篇文章&#xff0c;…

SpringBoot3.0不建議使用spring.factories,使用AutoConfiguration.imports新的自動配置方案

文章目錄 一、寫在前面二、使用imports文件1、使用2、示例比對3、完整示例 參考資料 一、寫在前面 spring.factories是一個位于META-INF/目錄下的配置文件&#xff0c;它基于Java的SPI(Service Provider Interface)機制的變種實現。 這個文件的主要功能是允許開發者聲明接口的…

鴻蒙特效教程10-卡片展開/收起效果

鴻蒙特效教程10-卡片展開/收起效果 在移動應用開發中&#xff0c;卡片是一種常見且實用的UI元素&#xff0c;能夠將信息以緊湊且易于理解的方式呈現給用戶。 本教程將詳細講解如何在HarmonyOS中實現卡片的展開/收起效果&#xff0c;通過這個實例&#xff0c;你將掌握ArkUI中狀…

hn航空app hnairSign unidbg 整合Springboot

聲明: 本文章中所有內容僅供學習交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包內容、敏感網址、數據接口等均已做脫敏處理&#xff0c;嚴禁用于商業用途和非法用途&#xff0c;否則由此產生的一切后果均與作者無關&#xff01; 逆向分析 學習unidbg補環境。先弄一個…

奇怪的異形選項卡樣式、弧形邊框選項卡

<template><div :class"$options.name"><div class"tab">默認選項卡</div><div class"tab" active>選中選項卡</div><el-divider /><el-tabs v-model"tabActiveName" tab-click"(t…

特殊行車記錄儀DAT視頻丟失的恢復方法

行車記錄儀是一種常見的車載記錄儀&#xff0c;和常見的“小巧玲瓏”的行車記錄儀不同&#xff0c;一些特種車輛使用的記錄儀的外觀可以用“笨重”來形容。下邊我們來看看特種車載行車記錄儀刪除文件后的恢復方法。 故障存儲: 120GB存儲設備/文件系統:exFAT /簇大小:128KB 故…

UE5小石子陰影在非常近距離才顯示的問題

Unreal中采用LandscapeGrass生成的地形&#xff0c;在MovieRenderQueue中渲染時陰影顯示距離有問題&#xff0c;在很近的時候才會有影子&#xff0c;怎么解決&#xff1f; 地面上通過grass生成的小石子的陰影只能在很近的時候才能顯示出來&#xff0c;需要如下調整 r.Shadow.R…

零基礎上手Python數據分析 (9):DataFrame 數據讀取與寫入 - 讓數據自由穿梭

回顧一下,上篇博客我們學習了 Pandas 的核心數據結構 Series 和 DataFrame。 DataFrame 作為 Pandas 的 “王牌” 數據結構,是進行數據分析的基石。 但 DataFrame 的強大功能,還需要建立在 數據輸入 (Input) 和 數據輸出 (Output) 的基礎上。 數據從哪里來? 分析結果又如何…

【商城實戰(65)】退換貨流程全解析:從前端到后端的技術實現

【商城實戰】專欄重磅來襲!這是一份專為開發者與電商從業者打造的超詳細指南。從項目基礎搭建,運用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用戶、商品、訂單等核心模塊開發,再到性能優化、安全加固、多端適配,乃至運營推廣策略,102 章內容層層遞進。無論是想…

SQL Server 2022 安裝問題

一、安裝與配置問題 1. SQL Server 2022 安裝失敗怎么辦&#xff1f; 常見原因&#xff1a; 硬件或操作系統不滿足最低要求&#xff08;如內存、磁盤空間不足&#xff09;。未關閉防火墻或殺毒軟件。之前版本的 SQL Server 殘留文件未清理。 解決方案&#xff1a; 確保硬件配…

解鎖 AWX+Ansible 自動化運維新體驗:快速部署實戰

Ansible 和 AWX 是自動化運維領域的強大工具組合。Ansible 是一個簡單高效的 IT 自動化工具&#xff0c;而 AWX 則是 Ansible 的開源 Web 管理平臺&#xff0c;提供圖形化界面來管理 Ansible 任務。本指南將帶你一步步在 Ubuntu 22.04 上安裝 Ansible 和 AWX&#xff0c;使用 M…

【xiaozhi贖回之路-2:語音可以自己配置就是用GPT本地API】

固件作用 打通了網絡和硬件的溝通 修改固件實現【改變連接到小智服務器的】 回答邏輯LLM自定義 自定義了Coze&#xff08;比較高級&#xff0c;自定義程度比較高&#xff0c;包括知識庫&#xff0c;虛擬腳色-戀人-雅思老師-娃娃玩具{可能需要使用顯卡對開源模型進行微調-產…

Springboot 學習 之 Shardingsphere 按照日期水平分表(二)

文章目錄 業務場景依賴配置特別注意優劣參考資料 業務場景 在 報表 等 大數據量 且需要 按照日期顯示 的業務場景下&#xff0c;按照 日期水平分表 是一個不錯的選擇 依賴 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-b…