当前位置: 首页 > news >正文

记录偶发更新失败问题

一,代码如下

 @Transactional(rollbackFor = Exception.class)
public void updateDelivery(){
// 1.新增反馈记录
// 2.更新订单状态,及其他字段
// 3.新增变更履历
// 4.其他新增逻辑及与其他系统交互逻辑
}

二,问题

偶尔出现(概率极低)步骤2中订单更新失败,状态没有更新,最初听说只是状态这个字段没有更新成功,其实还有其他字段,是都没有更新,即没有进行更新操作。具体更新逻辑是当前状态是10,想更新成20,结果还是10。

三,猜想与分析

猜想1,执行代码时出现了异常,但是经过查看步骤1,3,4都执行成功了,直到方法最后,并没有出现异常

猜想2,业务逻辑中该状态变更不符合具体场景,由于猜想1中1,3执行成功,且经过代码分析,不存在这种问题

猜想3,更新时出现了死锁,这条数据在其他地方也在更新,且对方持有该数据锁(行锁),导致这里更新失败

具体可能出现同时更新的场景有两个

  1. 有一个job会查询状态是10的订单数据,会写入一个中间表,然后更新这条订单数据,job更新成功

  1. 并发导致,两个请求同时调用该接口,请求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的时间完全问好

四,解决方案

  1. 调整job执行频率,由1min改成5分钟,避免同时取到订单数据进行更新

  1. job逻辑中改循环查询更新为直接更新,减少整个方法执行时间,缩短大事物

五,疑问

  1. 查看数据库死锁日志,并没有发现出现死锁记录,show engin innodb status,如果这个信息准确,又是什么原因没有更新成功呢

  1. 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更新逻辑抽离出来,使用编程式事物,同时把整个方法声明式事物去掉,这种改造目的是缩短大事物,但是好像又是针对并发场景下大事物问题,但是上面已经验证并发场景下,会顺序执行,等待前面事物结束

以上分析可能存在不足,欢迎大佬指正,不胜感激

http://www.lryc.cn/news/24563.html

相关文章:

  • AI环境搭建步骤(Windows环境)
  • Linux系统之history命令的基本使用
  • 花7000报了培训班,3个月后我成功“骗”进了阿里,月薪拿16K....
  • Java-枚举类的使用(详解)
  • Docker----------Docker轻量级可视化工具Portainer/监控之 CAdvisor+InfluxDB+Granfana
  • 景嘉微7201
  • 串口、终端应用程序 API termios
  • 【服务器搭建】教程七:如何为自己的网站添加运行时间?
  • 【消息中间件】Apache Kafka 教程
  • ARM基础
  • Python排序 -- 内附蓝桥题:错误票据,奖学金
  • 容器化部署是什么意思?有什么优势?
  • 1.设计模式简介
  • 【算法题解】实现一个包含“正负数和括号”的基本计算器
  • 网站服务器如何防护攻击?网站服务器被挂马如何检测
  • JavaSE16-面向对象-接口
  • 安卓设备蓝牙键盘快捷键
  • Puppeteer项目结构梳理
  • (02)Unity HDRP Volume 详解
  • 拒绝B站邀约,从月薪3k到年薪47W,我的经验值得每一个测试人借鉴
  • 分享一种实用redis原子锁的方式
  • 【华为OD机试】 字符串解密(C++ Java JavaScript Python)
  • 金三银四,助力你的大厂梦,2023年软件测试经典面试真题(1)(共3篇)
  • 假如面试官要你手写一个promise
  • 【leetcode】寻找重复数
  • LeetCode 1247. Minimum Swaps to Make Strings Equal【数学,贪心,字符串】
  • pid控制加热算法,附代码仓库
  • 一文看懂预训练和自训练模型
  • (五十四)大白话索引的页存储物理结构,是如何用B+树来实现的?.md
  • 前端Vue代码风格指南