记录偶发更新失败问题
一,代码如下
@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…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...