Redisson分布式锁实战

实战来源

此问题基于电商

这周遇见这么一个问题,简略的说一下

MQ发布了两个消息,一个是订单新增,一个是订单状态变更

由于直接付款之后,这两个消息的发布时间不分先后,可能会造成两种情况,1、订单状态变更在订单新增之前;2、订单新增在订单状态变更之前

逻辑二没有问题,有问题的是逻辑一,如果订单状态变更在订单新增之前,那么连新建订单都没有入库,怎么能改变订单的状态呢

为了完成这个逻辑就需要使用锁来让两个业务同步,必须让新建订单在订单状态变更之前,我这里使用的是基于Redisson的分布式Redis

前提,需要了解Redisson分布式锁、redis

redisson:Redis实现分布式锁原理和Redisson框架实现分布式锁,全网最详细讲解_

redis工具类:Redis工具类(redisTemplate)以及 redisTemplate 的用法

代码如下,从MQ取消息的的逻辑我就不写了

这里的代码还是有问题的,但在生产中95%可能遇不见,要么服务器炸了,要么redis挂了,要么其他人的接口被人发现漏洞随意调用,导致MQ传递参数有误,那么都是别人的问题,跟我们没关系

因为同一订单,订单新建和订单状态变更的订单号就是一样的,我用Redis做了以下三个K-V键值对

1、redis的key="前缀_STATUS_SYNCHRONIZATION_" + json.getString("订单号")

这个是,redis锁的key保证新建订单和订单状态变更的主要逻辑能同步valueredisson生成我们不用管,只用管超时时间就好了

2、redis的key="前缀_NEW_ORDER_" + json.getString("订单号")

这个是,判断是否新建订单在订单状态变更之前,有下述两种情况

  • 如果新建订单在订单状态变更之前,那么新建订单时会存入这个2中的redisK-V值,在订单状态变更的时候会用redis访问2中的key发现不为空(redis存在2中的K-V关系),就直接走入库操作
  • 如果订单状态变更在新建之前,访问redis2的这个key发现为空(redis不存在2中的K-V关系),就用下述3中的K-V把需要入库的订单状态变更的实体类存起来,由新建订单时候调用时。新建订单时发现下述3中的K-V存在就说明订单状态变更在新建订单之前,就需要把下述3中的V取出来做入库处理,否则就认为新建订单在订单状态变更之前,就不需要做处理

value无所谓是什么,不重要,我这里就写的value=等待付款 没什么特殊含义。设置了两个小时的超时时间,因为订单超过两个小时了就不让付款了

3、redis的key="前缀_STATUS_PUSH_" + json.getString("订单号")

这个是,如果订单状态变更在新建订单之前,那么我就存进去,由新建的时候判断是否存在这个值,如果存在就说明订单在状态变更之前,就取出这个key对应的value(因为我们存的就是订单状态变更时需要的数据)做入库处理,不存在说明新建订单在订单状态变更之前就不需要做处理

	/*** 我的redis工具类,在前提中有*/@Autowiredprivate RedisUtil redisUtil;/*** redisson,只需要配置一个配置类就可以使用了,可以看前提中redisson的文章*/@Resourceprivate Redisson redisson;/*** 订单状态变更* @param json* @return*/public boolean orderStatusChange(JSONObject json){try {// ...// 上面拼接需要入库的逻辑得到订单状态变更的实体类信息,不可能把源码展示出来,我仅放关键代码,order 就相当于从 MQ 获取的内容封装成了实体类 orderOrder order = new Order();// 主要看下面的代码// 只有付款的时候走此逻辑,因为可能出现,直接付款,状态推送和新建订单一起发过来且付款先执行的操作if (json.getString("code").equals("付款的code")){// 如果已付款是先执行就先缓存由新建调用RLock lock = redisson.getLock("前缀_STATUS_SYNCHRONIZATION_" + json.getString("订单号"));// 60秒自旋拿锁,拿到锁之后持有60秒 (如果后面的值 <0 的话会使用开门狗机制,一直持有锁,除非项目挂掉了)boolean success = lock.tryLock(60L, 60L, TimeUnit.SECONDS);try {// 判断新建订单执行了吗,因为我的逻辑是如果新建订单执行后会执行下述注解中的代码,存一个两小时的 redis 缓存,因为新建订单之后两个小时不付款就取消订单了// 这是新建订单后设置的值: redisUtil.set("前缀_NEW_ORDER_" + json.getString("订单号"),"已经新建订单",7200);if (redisUtil.get("前缀_NEW_ORDER_" + json.getString("订单号")) == null){// 如果为上诉为空,说明订单状态变更在订单新增之前,我们就缓存一下300秒,至于多少秒看着办吧,300就太多了,但不影响redisUtil.set("前缀_STATUS_PUSH_" + json.getString("订单号"),JSON.toJSONString(order),300);// 因为在之前就不需要执行之后的代码了,返回就行return true;}// 删除redis多余的keyredisUtil.delete("前缀_ORDER_" + json.getString("订单号"));} catch (Exception e) {logger.error("订单状态推送同步锁失效",e);}finally {// 判断当前线程是否持有锁if (success && lock.isHeldByCurrentThread()) {lock.unlock();}}}// 如果新增在订单状态变更之前就可以直接入库了,如果订单状态变更在新建之前的话,上面有return操作,就不会执行入库操作statusChangeReceiptOperation(order);return true;} catch (Exception e) {logger.error("订单状态变更 报错:",e);return false;}}/*** 订单状态变更入库操作* @param order*/private void statusChangeReceiptOperation(Order order){try {// 订单状态变更入库操作。。。} catch (Exception e) {logger.error("订单状态变更报错:",e);}}/*** 新建订单*/public boolean newOrder(JSONObject json){try {// 入库操作,因为订单新增肯定是最开始的,所以根据json的参数直接入库就行了,我就不写了// 。。。// 加和上述相同的锁,因为订单号是一样的RLock lock = redisson.getLock("前缀_STATUS_SYNCHRONIZATION_" + json.getString("订单号"));// 60秒自旋拿锁,拿到锁之后持有60秒 (如果后面的值 <0 的话会使用开门狗机制,一直持有锁,除非项目挂掉了)boolean success = lock.tryLock(60L, 60L, TimeUnit.SECONDS);try {// 设置两个小时缓存,超时就认为不付款了redisUtil.set("前缀_NEW_ORDER_" + json.getString("订单号"),"等待付款",7200);// 去拿订单状态变更设置的redis的k-v键值对Object o = redisUtil.get("前缀_STATUS_PUSH_" + json.getString("订单号"));// 判断这里为不为空,因为加了锁,为空说明新增在订单状态变更之前,不需要多余操作if (o != null){// 如果不为空,说明状况变更在新增之前,取值然后入库JSONObject out2 = JSON.parseObject(o.toString());// json转为实体类Order order = JSONObject.toJavaObject(out2,Order.class);// 入库statusChangeReceiptOperation(order);// 删除redis多余的keyredisUtil.delete("前缀_STATUS_PUSH_" + json.getString("订单号"));}} catch (Exception e) {logger.error("订单新建推送同步锁失效",e);}finally {// 判断当前线程是否持有锁if (success && lock.isHeldByCurrentThread()) {lock.unlock();}}return true;} catch (Exception e) {logger.error("新建导购商户订单:",e);return false;}}

我遇见的问题

Factory method 'redisson' threw exception; nested exception is java.lang.NoSuchMethodError: io.netty.util.NetUtil.isIpV4StackPreferred()Z

猜测原因:NetUtil没有redisson需要的isIpV4StackPreferred()方法

排查:发现两个相同的class文件,那么肯定是jar包冲突了

在这里插入图片描述

查看依赖:

在这里插入图片描述

发现是这个包有问题:

在这里插入图片描述

去pom文件中排除:

在这里插入图片描述

总结

这只是我的思路和解决方法,如果有大佬有更好的办法,希望可以劳烦跟我探讨一下,共同成长,万分感谢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/72358.shtml

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

相关文章

Spark2x原理剖析(二)

一、概述 基于社区已有的JDBCServer基础上&#xff0c;采用多主实例模式实现了其高可用性方案。集群中支持同时共存多个JDBCServer服务&#xff0c;通过客户端可以随机连接其中的任意一个服务进行业务操作。即使集群中一个或多个JDBCServer服务停止工作&#xff0c;也不影响用…

后端笔试题(2)分频器波形图

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口

算法通关村第十八关:青铜挑战-回溯是怎么回事

青铜挑战-回溯是怎么回事 回溯&#xff0c;最重要的算法之一 主要解决一些暴力枚举也搞不定的问题&#xff0c;例如组合、分割、子集、排列、棋盘等 从性能角度来看回溯算法的效率并不高&#xff0c;但对于这些暴力都搞不定的算法能出结果就很好了&#xff0c;效率低点没关系…

c++类与对象

文章目录 前言一、1、类的引入2、类的定义3、类的访问限定符及封装4、类的实例化5、类对象模型6、this指针7、封装 前言 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关…

MySQL聚簇索引与非聚簇索引

分析&回答 当数据库一条记录里包含多个字段时&#xff0c;一棵B树就只能存储主键&#xff0c;如果检索的是非主键字段&#xff0c;则主键索引失去作用&#xff0c;变成顺序查找了。这时应该在第二个要检索的列上建立第二套索引。这个索引由独立的B树来组织。有两种常见的方…

2023国赛数学建模B题思路分析 - 多波束测线问题

# 1 赛题 B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播&#xff0c; 在不同界面上产生反射&#xff0c; 利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信 号&#xff0c;并记录从声波发射到…

vue基于Echarts、百度地图MapVGL实现可视化大屏数据展示

一、布局 常见的大屏数据展示布局&#xff0c;一般会将地图作为整个屏幕的背景&#xff0c;在地图上以九宫格布局展示各类数据图表。实现这一效果可以使地图的z-index1,在地图上的图表等z-index>1,下面会详细描述这种设计该如何实现&#xff1a; <div style"width…

嵌入式开发笔试面试

C语言部分&#xff1a; 1.gcc的四步编译过程 1.预处理 展开头文件&#xff0c;删除注释、空行等无用内容&#xff0c;替换宏定义。 gcc -E hello.c -o hello.i 2.编译 检查语法错误&#xff0c;如果有错则报错&#xff0c;没有错误则生成汇编文件。 gcc -S hello.i -o h…

C++ vector模拟实现

目录 使用insert时迭代器失效使用erase时迭代器失效使用memcpy浅拷贝的问题调用最匹配的函数可能出现的问题模拟实现vector 使用insert时迭代器失效 在模拟vector插入的时候会遇到扩容后pos失效的问题&#xff0c;需要更新pos vector():_start(nullptr), _finish(nullptr), _e…

解耦只是一个巧合?

本文分享一篇在IJCAI2023看到的文章&#xff1a;Overlooked Implications of the Reconstruction Loss for VAE Disentanglement 首先回顾下VAE&#xff0c;其loss函数有两项&#xff0c;一项是重构误差&#xff0c;另一项是正则项&#xff1a; L r e c ( x , x ^ ) E q ϕ (…

QT(9.1)对话框与事件处理

作业&#xff1a; 1. 完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&…

23062C++QT day2

封装一个结构体&#xff0c;结构体中包含一个私有数组&#xff0c;用来存放学生的成绩&#xff0c;包含一个私有变量&#xff0c;用来记录学生个数&#xff0c; 提供一个公有成员函数&#xff0c;void setNum(int num)用于设置学生个数 提供一个公有成员函数&#xff1a;void…

FPGA实战小项目3

基于FPGA的波形发生器 基于FPGA的波形发生器 基于FPGA的beep音乐播放器设计 基于FPGA的beep音乐播放器设计 基于FPGA的cordic算法实现DDS sin和cosine波形的产生 基于FPGA的cordic算法实现DDS sin和cosine波形的产生

无需公网IP,实现远程访问群晖Drive并挂载为电脑磁盘的方法

文章目录 前言1.群晖Synology Drive套件的安装1.1 安装Synology Drive套件1.2 设置Synology Drive套件1.3 局域网内电脑测试和使用 2.使用cpolar远程访问内网Synology Drive2.1 Cpolar云端设置2.2 Cpolar本地设置2.3 测试和使用 3. 结语 前言 群晖作为专业的数据存储中心&…

利用MQ实现mysql与elasticsearch数据同步

流程 1.声明exchange、queue、RoutingKey 2. 在hotel-admin中进行增删改&#xff08;SQL&#xff09;&#xff0c;完成消息发送 3. 在hotel-demo中完成消息监听&#xff0c;并更新elasticsearch数据 4. 测试同步 1.引入依赖 <!--amqp--> <dependency><groupId&…

欧洲云巨头OVHcloud收购边缘计算专家 gridscale

边缘计算社区近日获悉&#xff0c;欧洲云巨头OVHcloud已进入全面收购德国公司 gridscale 的谈判&#xff0c;该公司是一家专门从事超融合基础设施的软件提供商。 此次战略收购将标志着 OVHcloud 的另一个重要里程碑&#xff0c;使该集团能够显着加速其地理部署&#xff0c;并进…

lv3 嵌入式开发-6 linux shell脚本编程(概念、变量、语句)

1 Shell脚本概述 1.1Shell脚本概述 Shell脚本是利用 shell 的功能所写的一个程序。这个程序是使用纯文本文件&#xff0c;将一些 shell 的语法与命令&#xff08;含外部命令&#xff09;写在里面&#xff0c;搭配正则表达式、管道命令与数据流重定向等功能 1.2Shell脚本编写流…

[JAVA] byte与int的类型转换案例剖析

总结&#xff1a; ①没有byte的字面值&#xff0c;赋值时需要强制转换类型 ②涉及运算&#xff0c;系统自动进行类型升级&#xff0c;由此用final修饰&#xff0c;代表这是一个不会更改值的常量&#xff0c;通过编译 感受&#xff1a;还是用int吧&#xff0c;自动类型转换太复…

Java程序员所需Javascript知识

它是一种脚本语言&#xff0c;可以用来更改页面内容&#xff0c;控制多媒体&#xff0c;制作图像、动画等等 js 代码位置 <script>// js 代码 </script>引入 js 脚本&#xff0c;在js脚本中写js代码 <script src"js脚本路径"></script>注…

滑动窗口实例7(串联所有单词的子串)

题目&#xff1a; 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff0c;…