记录偶发更新失败问题
一,代码如下
@Transactional(rollbackFor = Exception.class)
public void updateDelivery(){
// 1.新增反馈记录
// 2.更新订单状态,及其他字段
// 3.新增变更履历
// 4.其他新增逻辑及与其他系统交互逻辑
}
二,问题
偶尔出现(概率极低)步骤2中订单更新失败,状态没有更新,最初听说只是状态这个字段没有更新成功,其实还有其他字段,是都没有更新,即没有进行更新操作。具体更新逻辑是当前状态是10,想更新成20,结果还是10。
三,猜想与分析
猜想1,执行代码时出现了异常,但是经过查看步骤1,3,4都执行成功了,直到方法最后,并没有出现异常
猜想2,业务逻辑中该状态变更不符合具体场景,由于猜想1中1,3执行成功,且经过代码分析,不存在这种问题
猜想3,更新时出现了死锁,这条数据在其他地方也在更新,且对方持有该数据锁(行锁),导致这里更新失败
具体可能出现同时更新的场景有两个
有一个job会查询状态是10的订单数据,会写入一个中间表,然后更新这条订单数据,job更新成功
并发导致,两个请求同时调用该接口,请求1想更新成10,请求2想更新成20,由于业务逻辑比较长,请求1持有该数据锁,请求2无法更新成功,可能出现了死锁
针对场景2具体再分析
实际上,两次请求应该都会更新成功,请求2会等待请求1事物结束,然后再去更新数据,也不会出现死锁,可以模拟下这种并发场景。
具体代码如下,请求1,进来,更新后休眠20s,保证事物不结束,保证持有该数据行锁(这个应该是这样),请求2进来,并没有出现异常,且状态也会正常更新。
@ApiOperation(value = "模拟更新订单主表异常-并发1", notes = "模拟更新订单主表异常-并发1")@PostMapping(value = "updateDeliveryException1")@Transactional(rollbackFor = Exception.class)public PpDelivery updateDeliveryException1(@RequestBody PpDelivery ppDelivery) {tiExceptionLogService.insertExceptionRecord(ppDelivery, "", "模拟事物问题");for (int i = 0; i < 3; i++) {Integer result = 0;Boolean isException = false;try {result = ppDeliveryService.updatePpDelivery(ppDelivery);Thread.sleep(20000);if (result == 1) {break;}} catch (Exception e) {logger.error("ylToDPSOrderStatusFeedbackImpl update exception ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue), e);tiExceptionLogService.insertExceptionRecord(ppDelivery, e.getMessage(), "更新订单主表失败,数据库执行新增异常");isException = true;}if (result == 0 && Boolean.FALSE.equals(isException)) {logger.error("ylToDPSOrderStatusFeedbackImp update failed ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue));tiExceptionLogService.insertExceptionRecord(ppDelivery, String.valueOf(result), "更新订单主表失败,数据库执行新增未返回成功");}try {Thread.sleep(1000);} catch (InterruptedException e) {logger.error("ylToDPSOrderStatusFeedbackImpl interruptedException ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue));}}return ppDelivery;}
@ApiOperation(value = "模拟更新订单主表异常-并发2", notes = "模拟更新订单主表异常-并发2")@PostMapping(value = "updateDeliveryException2")@Transactional(rollbackFor = Exception.class)public PpDelivery updateDeliveryException2(@RequestBody PpDelivery ppDelivery) {tiExceptionLogService.insertExceptionRecord(ppDelivery, "", "模拟事物问题");for (int i = 0; i < 3; i++) {Integer result = 0;Boolean isException = false;try {result = ppDeliveryService.updatePpDelivery(ppDelivery);if (result == 1) {break;}} catch (Exception e) {logger.error("ylToDPSOrderStatusFeedbackImpl update exception ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue), e);tiExceptionLogService.insertExceptionRecord(ppDelivery, e.getMessage(), "更新订单主表失败,数据库执行新增异常");isException = true;}if (result == 0 && Boolean.FALSE.equals(isException)) {logger.error("ylToDPSOrderStatusFeedbackImp update failed ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue));tiExceptionLogService.insertExceptionRecord(ppDelivery, String.valueOf(result), "更新订单主表失败,数据库执行新增未返回成功");}try {Thread.sleep(1000);} catch (InterruptedException e) {logger.error("ylToDPSOrderStatusFeedbackImpl interruptedException ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue));}}return ppDelivery;}
通过以上分析,如果排除场景2,很可能就是场景1了。且查询更新时间job中这条数据更新与最初代码中步骤3的时间完全问好
四,解决方案
调整job执行频率,由1min改成5分钟,避免同时取到订单数据进行更新
job逻辑中改循环查询更新为直接更新,减少整个方法执行时间,缩短大事物
五,疑问
查看数据库死锁日志,并没有发现出现死锁记录,show engin innodb status,如果这个信息准确,又是什么原因没有更新成功呢
job中并没有声明式事物,会存在大事物问题吗,代码如下
/** 订单状态存中间表定时器*
*/
@JobHandler(value = "orderStatusToTiHandler")
@Component
public class OrderStatusToTiHandler extends IJobHandler {@Autowiredprivate OrderStatusDispatchToAllService orderStatusDispatchToAllService;@Autowiredprivate IPpDeliveryService ppDeliveryService;@Autowiredprivate IBasCarrierService basCarrierService;@Autowiredprivate ISysSettingService sysSettingService;private static final Logger logger = LoggerFactory.getLogger(OrderStatusToTiHandler.class);@Overridepublic ReturnT<String> execute(String s){logger.info("订单状态存中间表定时任务开始运行运行");List<String> statusNotIn = Arrays.asList("66","90", "99");List<String> statusIn = Arrays.asList("6020", "6025", "6050", "6055", "100","6045");PpDelivery ppDeliveryQurey = new PpDelivery();ppDeliveryQurey.setInterfaceStatus("0");ppDeliveryQurey.setSortName("delivery_no");ppDeliveryQurey.setSortOrder("ASC");List<PpDelivery> ppDeliveries = ppDeliveryService.queryAllByValuesAndStatus(ppDeliveryQurey, statusIn.toArray(new String[statusIn.size()]), statusNotIn.toArray(new String[statusNotIn.size()]));if (ppDeliveries.size() <= 0) {logger.info("订单状态存中间表定时任务运行成功");return SUCCESS;}for (PpDelivery ppDelivery : ppDeliveries) {if(StringUtils.isNotBlank(ppDelivery.getTmBasCarrierId())){String receiver = this.judgeCarrierPlatform(ppDelivery.getTmBasCarrierId());if(StringUtils.isNotBlank(receiver)){orderStatusDispatchToAllService.orderStatusFeedBackToTi(ppDelivery.getTtPpDeliveryId(), receiver+"_" + ppDelivery.getCurrentStatus());}}}logger.info("订单状态存中间表定时任务运行成功");return SUCCESS;}
3,改声明式事物为编程式事物,有用吗
如果把最初的代码中,步骤2更新逻辑抽离出来,使用编程式事物,同时把整个方法声明式事物去掉,这种改造目的是缩短大事物,但是好像又是针对并发场景下大事物问题,但是上面已经验证并发场景下,会顺序执行,等待前面事物结束
以上分析可能存在不足,欢迎大佬指正,不胜感激
相关文章:
记录偶发更新失败问题
一,代码如下Transactional(rollbackFor Exception.class) public void updateDelivery(){ // 1.新增反馈记录 // 2.更新订单状态,及其他字段 // 3.新增变更履历 // 4.其他新增逻辑及与其他系统交互逻辑 }二,问题偶尔出现(概率极低…...
AI环境搭建步骤(Windows环境)
1. 安装好Anaconda3版本(1) 安装链接:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/?CM&OD本文使用Anaconda3下载链接:Anaconda5(2) 注意安装anaconda时一定要把环境变量加入windows环境中。要没有勾选,安装完后还有手动加入…...
Linux系统之history命令的基本使用
Linux系统之history命令的基本使用一、history命令介绍二、本地环境检查1本地系统版本2.检查操作系统的内核版本三、history的命令帮助四、history命令的基本帮助1.查看所有历史执行命令2.指定历史命令条数3.清除历史命令记录4.引用历史命令5.将历史文件中的信息读入到当前缓冲…...

花7000报了培训班,3个月后我成功“骗”进了阿里,月薪拿16K....
“月薪4000元不如报名学IT,挑战年薪百万”这是大多数培训班在互联网上宣传的口号,简单的16个字却戳中了很多人的痛点,同龄人买车买房,自己却拿着微薄的工资连好一点的房子都租不起,这句口号 彻底激起了底层员工的焦虑&…...

Java-枚举类的使用(详解)
枚举类的使用前言一、何为枚举类?二、自定义枚举类(JDK1.5之前)1、实现1.1 属性1.2 构造器2、代码演示三、用关键字enum定义枚举类(JDK 1.5)1、实现1.1 属性1.2 构造器2、代码演示四、Enum类的方法五、实现接口的枚举类…...

Docker----------Docker轻量级可视化工具Portainer/监控之 CAdvisor+InfluxDB+Granfana
1.是什么 Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便地管理Docker环境,包括单机环境和集群环境。 2 官网 官网 https://www.portainer.io/ https://docs.portainer.io/v/ce-2.9/start/install/server/docker/linux 3.…...
景嘉微7201
220112-驱动与固件-景嘉微7201驱动与固件-三期超翔TF830JM7201显卡黑屏、花屏、竖线或待机唤醒黑屏JM72系列为了让驱动和系统内核解绑,驱动包含核内和核外两个驱动,两个驱动请都务必安装;最近JM7201 替代R7 340 发货了,导致对应通…...

串口、终端应用程序 API termios
UART简介 串口全称为串行接口,也称为COM接口,串行接口指的是比特一位位顺序传输,通信线路简单。使用两根线就可以实现双向通信,一条为TX,一个为RX。串口通信距离远,但速度相对慢,是一种常用的工…...

【服务器搭建】教程七:如何为自己的网站添加运行时间?
前言 哈喽,大家好,我是木易巷! 上一篇服务器搭建个人网站教程是给大家介绍了:网站如何添加备案号? 今天分享:如何为自己的网站添加运行时间? 木易巷添加网页运行时间后的效果 其实和昨天的添…...

【消息中间件】Apache Kafka 教程
文章目录Apache Kafka 概述什么是消息系统?点对点消息系统发布 - 订阅消息系统什么是Kafka?好处用例需要KafkaApache Kafka 基础(一)消息系统1、点对点的消息系统2、发布-订阅消息系统(二)Apache Kafka 简介…...

ARM基础
文章目录1.ARM成长史1.1 ARM发展的里程碑11.2 ARM发展的里程碑21.3 ARM发展的里程碑31.4 ARM发展的里程碑42.ARM的商业模式和生态系统3.先搞清楚各种版本号3.1 ARM 的型号命名问题3.2 ARM 的几种版本号3.3 ARM型号的发展历程4.SoC和CPU的区别 & 外设概念的引入4.1 SoC和CPU…...

Python排序 -- 内附蓝桥题:错误票据,奖学金
排序 ~~不定时更新🎃,上次更新:2023/02/28 🗡常用函数(方法) 1. list.sort() --> sort 是 list 的方法,会直接修改 list 举个栗子🌰 li [2,3,1,5,4] li.sort() print(li) …...
容器化部署是什么意思?有什么优势?
多小伙伴不知道容器化部署是什么意思?不知道容器化部署有什么优势?今天我们就来一起看看。 容器化部署是什么意思? 容器化部署是指将软件代码和所需的所有组件(例如库、框架和其他依赖项)打包在一起,让它…...
1.设计模式简介
一、设计模式的目的 1. 代码重用性 2. 可读性 3. 可扩展性 4. 可靠性 5. 高内聚,低耦合 二、设计模式七大原则 1. 单一职责原则 1)降低类的复杂度,一个类只负责一项职责 2)提高类的可读性,可维护性 3&#x…...
【算法题解】实现一个包含“正负数和括号”的基本计算器
这是一道 困难 题。 题目来自:leetcode 题目 给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 注意: 不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。 提示: s 由数字、‘’、‘-’…...
网站服务器如何防护攻击?网站服务器被挂马如何检测
网站服务器是指安装在互联网上的服务器,主要用于提供网站服务。由于网站服务器的重要性,它也是攻击者的活动焦点,因此如何防护攻击就显得尤为重要。本文将分析网站服务器是如何被攻击的以及如何防护攻击。 网站服务器是怎么被攻击的? 网站…...
JavaSE16-面向对象-接口
文章目录一、概念二、格式1.使用interface来定义接口2.implements实现接口三、接口中的成员1.常用成员2.新增成员(不重要)2.1 默认方法2.2 静态方法2.3 私有方法四、继承关系 & 实现关系五、抽象类和接口的使用区别一、概念 接口就是规范\规则&…...
安卓设备蓝牙键盘快捷键
安卓设备蓝牙键盘快捷键前言注意鼠标按键系统快捷键桌面快捷键输入法快捷键其它快捷键旧快捷键(已失效)前言 安卓设备可以通过蓝牙或有线外接键盘,值得一提的是,安卓平板连接蓝牙键盘和蓝牙鼠标是一个不错的组合。本文以鸿蒙3.0平…...

Puppeteer项目结构梳理
最近接触了一个个人感觉很奈斯的项目,故记录思路如下: puppeteer项目梳理: 入口文件 run.js 入口命令 node run.js YourConfig.json 1、我们可以在自己的config.json里面设置好 ①、登录的用户名密码;aws或其它服务器的access等id,accessKey…...

(02)Unity HDRP Volume 详解
1.概述这篇文章主要针对HDRP中的Volume和Volume Post-processing进行解释,针对于各个组件只能进行部分参数的解释,具体的信息可参考官方资料,这里只是对官方文档的图片效果补充以及笔者自己的理解。看到这里进入正文,请确保你的Un…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...