基于Redis分布锁+事务补偿解决数据不一致性问题

news2025/3/25 5:13:17

基于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);  // 成功:库存15
        service.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.coloradmin.cn/o/2320238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虚拟电商-延迟任务系统的微服务改造(二)注册中心和Feign调用

一、微服务注册中心Consul 编写完延迟任务系统的web层接口&#xff0c;也就是说可以基于http协议来访问延迟系统&#xff0c;接下来要将延迟任务改造成一个服务。首要考虑的问题就是服务的注册与发现&#xff0c;服务的注册与发现都离不开服务的注册中心&#xff0c;本项目选取…

数智读书笔记系列022《算力网络-云网融合2.0时代的网络架构与关键技术》读书笔记

一、书籍核心价值与定位 1.1 书籍概述:中国联通研究院的权威之作 《算力网络 —— 云网融合 2.0 时代的网络架构与关键技术》由中国联通研究院算力网络攻关团队精心撰写,是业界首部系统性探讨云网融合 2.0 与算力网络的专著。在云网融合从 1.0 迈向 2.0 的关键节点,本书的…

第十六届蓝桥杯康复训练--6

题目链接&#xff1a;790. 数的三次方根 - AcWing题库 思路&#xff1a;二分&#xff0c;注意正负号和小数判断退出的方法&#xff08;虽然正负无所谓&#xff09; 代码&#xff1a; #include<bits/stdc.h> using namespace std;#define exs 0.00000018812716007232667…

logisim安装以及可能出现的问题

阅读提示&#xff1a;我这篇文章更偏向于安装出现问题的解决方案 目录 一、安装步骤 二、安装问题 1、出错的问题 2、出错的原因与解决方法 一、安装步骤 1、下载logisim 官方网站&#xff1a;https://sourceforge.net/projects/circuit/ 下载适用于你操作系统的版本&…

Servlet、HttpServletRequest、HttpServletResponse、静态与动态网页、jsp、重定向与转发

DAY15.2 Java核心基础 JavaWeb 要想通过浏览器或者客户端来访问java程序&#xff0c;必须通过Servlet来处理 没有Servlet&#xff0c;java是无法处理web请求的 Web交互&#xff1a; 接收请求HttpServletRequest&#xff1a;可以获取到请求的信息&#xff0c;比如uri&#…

hackmyvm-Icecream

arp-scan -l nmap -sS -v 192.168.222.106 enum4linux 192.168.222.106 445端口 smbmap -H 192.168.222.106 icecream为只读模式 smbclient \\192.168.222.106\icecream 反弹shell(上传put php-reverse-shell.php) 开启监听 nc -lnvp 1234 拿到webshell cat /etc/passwd 9000端…

告别低效人工统计!自动计算计划进度

实时监控任务进度一直是项目管理中的一项巨大挑战。 人工统计方式不仅耗时耗力&#xff0c;而且往往由于信息传递的延迟和人为误差&#xff0c;导致无法实时获得准确的项目进展信息。 这种不准确性可能掩盖潜在的风险点&#xff0c;从而影响项目的整体进度和成果。 Ganttable …

AI比人脑更强,因为被植入思维模型【16】反脆弱

毛选中就有言&#xff0c;不经历困难&#xff0c;我们就不会掌握战胜困难的方法。 这个世界纷繁复杂&#xff0c;不是强者总是运气好&#xff0c;而是他们能够失败后快速复原&#xff0c;不断找到战胜困难的方法。 定义 马斯洛需求层次模型是一种将人类需求从低到高按层次进…

L2TP实验

放开安全策略机制&#xff0c;FW1不配IP [FW1]firewall zone trust [FW1-zone-trust]add interface GigabitEthernet 1/0/0 [FW1]security-policy [FW1-policy-security]default action permit FW2 和FW3 [FW2]interface g1/0/1 [FW2-GigabitEthernet1/0/1]ip address 2…

【数据预测】基于遗传算法GA的LSTM光伏功率预测 GA-LSTM光伏功率预测【Matlab代码#91】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 遗传算法GA2. 长短期记忆网络LSTM3. 基于GA-LSTM的光伏功率预测4. 部分代码展示5. 运行结果展示6. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】 …

【记录一下】LMDeploy学习笔记及遇到的问题

LMDeploy 是一个用于大型语言模型&#xff08;LLMs&#xff09;和视觉-语言模型&#xff08;VLMs&#xff09;压缩、部署和服务的 Python 库。 其核心推理引擎包括 TurboMind 引擎和 PyTorch 引擎。前者由 C 和 CUDA 开发&#xff0c;致力于推理性能的优化&#xff0c;而后者纯…

HC-05与HC-06蓝牙配对零基础教程 以及openmv识别及远程传输项目的概述

这个是上一年的项目&#xff0c;之前弄得不怎么完整&#xff0c;只有一个openmv的&#xff0c;所以openmv自己去我主页找&#xff0c;这篇主要讲蓝牙 这个是我在使用openmv连接单片机1然后单片机1与单片机2通过蓝牙进行通信 最终实现的效果是&#xff1a;openmv识别到图形和数…

Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测

Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测 目录 Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预…

OpenLayers集成天地图服务开发指南

以下是一份面向GIS初学者的OpenLayers开发详细教程&#xff0c;深度解析代码&#xff1a; 一、开发环境搭建 1.1 OpenLayers库引入 <!-- 使用CDN引入最新版OpenLayers --> <link rel"stylesheet" href"https://cdn.jsdelivr.net/npm/ollatest/ol.c…

VBA-Excel

VBA 一、数据类型与变量 常用数据类型&#xff1a; Byte&#xff1a;字节型&#xff0c;0~255。Integer&#xff1a;整数型&#xff0c;用于存储整数值&#xff0c;范围 -32768 到 32767。Long&#xff1a;长整型&#xff0c;可存储更大范围的整数&#xff0c;范围 -214748364…

OpenHarmony 开源鸿蒙北向开发——linux使用make交叉编译第三方库

这几天搞鸿蒙&#xff0c;需要编译一些第三方库到鸿蒙系统使用。 头疼死了&#xff0c;搞了一个多星期总算搞定了。 开贴记坑。 一、SDK下载 1.下载 在linux下使用命令 wget https://cidownload.openharmony.cn/version/Master_Version/OpenHarmony_5.1.0.54/20250313_02…

【第14届蓝桥杯C/C++B组省赛】01串的熵

问题描述 算法思想 首先分析题目中给出的公式 S 100时&#xff0c;其信息熵为 H(S)−p(0)log2​(p(0)) − p(0)log2​(p(0)) − p(1)log2​(p(1)) 继续化简公式得 设0出现的次数为x&#xff0c;1出现的次数为3-x H(S)−x * p(0) * log2​(p(0)) − (3-x) * p(1) * log2​(p(1)…

鸿蒙harmonyOS笔记:练习CheckBoxGroup获取选中的值

除了视觉效果实现全选和反选以外&#xff0c;咱们经常需要获取选中的值&#xff0c;接下来看看如何实现。 核心步骤&#xff1a; 1. 给 CheckBoxGroup 注册 onChange。 2. CheckBox 添加 name 属性。 3. 在 onChange 的回调函数中获取 选中的 name 属性。 事件&#xff1a…

收数据花式画图plt实战

目录 Python plt想把纵坐标化成对数形式代码 子图ax. 我又有ax scatter&#xff0c;又有ax plot&#xff0c;都要去对数 数字接近0&#xff0c;取对数没有定义&#xff0c;怎么办 创建数据 添加一个小的常数以避免对数未定义的问题 创建一个figure和一个子图ax 在子图a…

系统架构书单推荐(一)领域驱动设计与面向对象

本文主要是个人在学习过程中所涉猎的一些经典书籍&#xff0c;有些已经阅读完&#xff0c;有些还在阅读中。于我而言&#xff0c;希望追求软件系统设计相关的原则、方法、思想、本质的东西&#xff0c;并希望通过不断的学习、实践和积累&#xff0c;提升自身的知识和认知。希望…