麻了,这让人绝望的大事务提交

这篇具有很好参考价值的文章主要介绍了麻了,这让人绝望的大事务提交。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

继上次的if else优化也有段时间了,最近小猫又又又着道了,接手的那个项目又遇到了坑爹的地方,经常性的报死锁异常,经常性的主从延迟......通过报错信息按图索骥,发现代码是这样的。

这是一段商品发布的逻辑,我们可以看到参数校验、查询、最终的insert以及update全部揉在一个事务中。遇到批量发布商品的时候就经常出现问题了,数据库主从延迟是肯定少不了的。

开启优化

其实像上述小猫遇到的这种状况我们就称其为大事务,那么我们就大概有这么一个定义。我们将执行时间长,并且操作数据比较多的事务叫做大事务。

大事务产生的原因

在我们日常开发过程中,其实经常会遇到大事务,老猫总结了一下,往往原因其实总结下来有这么几点(当然存在纰漏的地方,也欢迎大家评论区留言补充)

  1. 一次性操作的数据量确实多,大量的锁竞争,比如批量操作这种行为。
  2. 事务粒度过大,代码中的 @Transactional使用不当,其他非DB操作比较多,耗时久。比如调用RPC接口,在例如上述小猫遇到的check逻辑甚至都揉在一起等等。

造成的影响

那么大事务造成的影响又是什么呢?

  1. 从开发者的角度来看的话,部分大事务必定对应的复杂的业务逻辑,代码封装事务拆解不合理,研发侧维护困难,维护成本高。
  2. 从最终系统以及运维角度来看
    • 出现了死锁。
    • 造成了主从延迟。
    • 大事务消耗更多的磁盘空间,回滚成本高。
    • 大事务发生的过程中,由于连接池持续被打开,很容易造成数据库连接池被沾满。
    • 接口响应慢导致接口超时,甚至导致服务不可用等等
      (欢迎大家补充)

优化方案

大事务既然有这么多坑,那么我们来看一下我们日常开发过程中,应该如何做到尽量规避呢?老猫整理了以下几种优化方法。

  1. 降低事务颗粒度,大事务拆解小事务
    • 编程式事务代替@Transactional。
    • 非update以及insert动作外移。
  2. 大数据量一次性提交尽可能拆解分批处理。
  3. 拆解原始事务,异步化处理。
降低事务颗粒度

1、我们对@Transactional的事务粒度把控不好,有时候如果使用不当的话事务功能可能会失效,如果经验不足,很难排查,那么我们不如直接使用粗细粒度更好把控的编程式事务。TransactionTemplate。这样的话咱们的优化代码就可以写好才能如下方式。

@Autowired
private TransactionTemplate transactionTemplate;

public boolean publishProduct(PublishProductRequest request) {
        externalSellerAuthorizeService.checkAuthorizeValid(request.getSellerId(),request.getThirdCategoryId(),request.getBrandId());
        ......
        transactionTemplate.execute((status) -> {
            try{
                //执行insert
                productDao.insert(productDO);
                productDescDao.insert(productDescDO);
                ....
                //其他insert以及update操作
            }catch (Exception e) {
                //回滚
                status.setRollbackOnly();
                return true;
            }
            return false;
        });
        return true;
    }
非update以及insert动作外移。

原始代码:

@Transactional(rollbackFor=Exception.class)
   public void save(Req req) {
         checkParam(req);
         saveData1(req);
         updateData2(req);
   }

   private void checkParam(Req req){
       Data1 data = selectData1();
       Data2 data2 = selectData2();
       if(data.getSomeThing() != STATUS_YES){
          throw new BusinessTimeException(.....);
       }
   }

然后部分小伙伴就觉得外移么,如果不用@Transactional的情况,那直接这样不就行了么。

错误改造案例:

class ServiceAImpl implements ServiceA {
  @Transactional(rollbackFor=Exception.class)
   public void save(Req req) {
         saveData1(req);
         updateData2(req);
   }

   private void checkParam(Req req){
       Data1 data = selectData1();
       Data2 data2 = selectData2();
       if(data.getSomeThing() != STATUS_YES){
          throw new BusinessTimeException(.....);
       }
   }

 public void save(Req req){
    checkParam(req);
    doSave(req);
 }
}

这个例子是非常经典的错误,这种直接方法调用的做法事务不会生效,老猫以前也踩过这样的坑。因为 @Transactional 注解的声明式事务是通过 spring aop 起作用的,
而 spring aop 需要生成代理对象,直接方法调用使用的还是原始对象,所以事务不会生效。那么我们应该如何改造呢?我们看下正确的改造。

正确改造方案1,当然还是利用上面的TransactionTemplate:

  @Autowired
  private TransactionTemplate transactionTemplate;

   public void save(Req req) {
         checkParam(req);
         transactionTemplate.execute((status) -> {
            try{
                saveData1(req);
                updateData2(req);
                ....
                //其他insert以及update操作
            }catch (Exception e) {
                //回滚
                status.setRollbackOnly();
                return true;
            }
            return false;
        });
   }

   private void checkParam(Req req){
       Data1 data = selectData1();
       Data2 data2 = selectData2();
       if(data.getSomeThing() != STATUS_YES){
          throw new BusinessTimeException(.....);
       }
   }

正确改造方案2,把 @Transactional 注解加到新Service方法上,把需要事务执行的代码移到新方法中。

  @Servcie
  public class ServiceA {
     @Autowired
     private ServiceB serviceB;

     private void checkParam(Req req){
       Data1 data = selectData1();
       Data2 data2 = selectData2();
       if(data.getSomeThing() != STATUS_YES){
          throw new BusinessTimeException(.....);
       }
   }

    public void save(Req req) {
          checkParam(req);
          serviceB.save(req);
    }
  }

   @Servcie
   public class ServiceB {
      @Transactional(rollbackFor=Exception.class)
      public void save(Req req) {
         saveData1(req);
         updateData2(req);
      }
   }

正确改造方案3:将ServiceA 再次注入到自身(老猫觉得这种方式不优雅,不太推荐,这里就不写了)

大数据量一次性提交尽可能拆解分批处理。

我们再来看大数量批量请求的场景,咱们具体来分析一下,假设上游系统存在一个批量导入2w的数据操作。如果我们读取到上游导入的数据,并且直接执行DB一次性执行肯定是不合适的。这种情况就需要我们对其请求的数据量做一个拆解。我们可以采用Lists.partition等等方式将数据拆成多个小的批量然后再进行入库操作处理。

@Servcie
public class ServiceA {
  @Autowired
  private ServiceB serviceB;

  private void batchAdd(List<Long> inventorySkuIdList){
      List<List<Long>> partition = Lists.partition(inventorySkuIdList, 1000);
        for (List<Long> idList : partition) {
            List<InventorySkuDO> inventorySkuDOList = inventorySkuDao.selectByIdList(idList, null);
            if (CollectionUtils.isNotEmpty(inventorySkuDOList)) {
               serviceB.doInsertUpdate(inventorySkuDOList);
            }
        }
  }
}

@Servcie
public class ServiceB {
  @Transactional(rollbackFor=Exception.class)
  private void doInsertUpdate(List<InventorySkuDO> inventorySkuDOList){
        for (InventorySkuDO inventorySkuDO : inventorySkuDOList) {
           doInsert(inventorySkuDO);
           doUpdate(inventorySkuDO)
        }
  }
}
拆解原始事务,异步化处理。

这种异步化处理的方案其实有两种方式进行异步化操作。尤其是涉及到第三方RPC调用或者HTTP调用的时候,这种方案就更加适合。

方案一,采用CompletableFuture异步编排特性,当业务流程比较长的时候,我们可以将一个大业务拆解成多个小的任务进行异步化执行。比如咱们有个批量支付的业务逻辑,因为整个流程是同步的,所以大概有了下面这样的流程。(关于CompletableFeature老猫觉得挺有意思的,后续老猫会出专门的文章来理透该特性,欢迎大家持续关注)。

对应转换成代码逻辑的话,大概是这样的:

void doBatchPay() {
        CompletableFuture<Object> task1 = CompletableFuture.supplyAsync(() -> {
            return "订单信息";
        });
        CompletableFuture<Object> task2 = CompletableFuture.supplyAsync(() -> {
            try {
               return doPay();
            } catch (InterruptedException e) {
                //log add
            }
        });

        //task1、task2 执行完执行task3 ,需要感知task1和task2的执行结果
        CompletableFuture<Object> future = task1.thenCombineAsync(task2, (t1, t2) -> {
            return "邮件发送成功";
        });
    }

方案二,Mq异步化处理,还是针对上述业务逻辑,我们是否可以将最终的发送邮件的动作剥离出来,最终再去统一执行发送邮件。


关于伪代码这里不展开了,有兴趣的小伙伴可以自行实现一下。

总结

虽然有时候业务催的确实比较急,我们也不得不加班加点赶工撸代码。但是我们不能由于这样的原因而舍弃对系统性能的追求。有人说反正这个项目我后面不维护的,坑的话还是留一个下一个人去解决吧,代码能跑就行,在此老猫还是想奉劝一句“研发何必为难研发”。在日常开发的过程中不仅仅是上面这样的大事务问题,其实还有很多优化的点,例如对象的创建,接口幂等,重试容错等等。后续老猫会持续分享近年来的经验,可能不是最好的,但是希望对你有用,当然也希望大家能够给出宝贵建议,欢迎大家持续关注。文章来源地址https://www.toymoban.com/news/detail-776898.html

到了这里,关于麻了,这让人绝望的大事务提交的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • spring项目里的大事务优化

    声明式事务只需要加在方法头加@Transactional注解即可开启事务,但是还是不太灵活,意味着整个方法所进行对数据库操作都要加进事务,当然一次查询也要进入事务,这并不是我们想要的,我们在update、insert操作上进行事务操作,方便进行回滚。 在使用事务之前,我们都应该

    2024年02月05日
    浏览(38)
  • 解决git上传远程仓库时的大文件提交

    在git中超过100M的文件会上传失败,而当一个文件超过50M时会给你警告,如下 解决这种问题,首先在项目的.git文件夹中找到.gitignore文件,并打开它进行编辑。在这个文件中,添加一行代码来忽略大文件的git上传。例如忽略jar: 接下来,使用Git LFS(Large File Storage)来管理大文

    2024年02月11日
    浏览(42)
  • 云计算实验4 面向行业背景的大数据分析与处理综合实验

    掌握分布式数据库接口Spark SQL基本操作,以及训练综合能力,包括:数据预处理、向量处理、大数据算法、预测和可视化等综合工程能力 Linux的虚拟机环境和实验指导手册 完成Spark SQL编程实验、交通数据综合分析平台环境部署和综合实验。 请按照实验指导手册,完成以下实

    2024年02月02日
    浏览(51)
  • 纵然是在产业互联网的时代业已来临的大背景下,人们对于它的认识依然是短浅的

    纵然是在产业互联网的时代业已来临的大背景下,人们对于它的认识依然是短浅的。这样一种认识的最为直接的结果,便是我们看到了各式各样的产业互联网平台的出现。 如果一定要找到这些互联网平台的特点的话,以产业端为出发点,无疑是它的最大的特点之一。很显然,

    2023年04月21日
    浏览(49)
  • springboot手动提交事务

    要手动提交事务,你需要在代码中获取当前的事务并调用它的 commit 方法。 在 Spring Boot 中,你可以通过在你的类中注入 PlatformTransactionManager 来获取当前的事务。然后,你可以使用 TransactionTemplate 类来手动执行事务。 例如: 在上面的代码中,我们使用 TransactionTemplate 的 exec

    2024年02月15日
    浏览(41)
  • springboot项目中手动提交事务

    @Service 层代码 Mapper接口层代码: insert1()和insert2()的插入SQL controller层代码 数据库表结构及初始数据 场景 在spring的声明式事务 @Transactional(rollbackFor = Exception.class) 的类XlServiceImpl中: 有其中一个方法 doInsert() 调用另外一个方法 mi() 。 doInsert() 会调用Mapper接口的 insert1() 方法向数

    2024年02月03日
    浏览(33)
  • 分布式事务(4):两阶段提交协议与三阶段提交区别

    1 两阶段提交协议 两阶段提交方案应用非常广泛,几乎所有商业OLTP数据库都支持XA协议。但是两阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。 缺点: 如果协调者宕机,参与者没有协调者指挥,则会一直阻塞。、 如下图: 第一阶段: 准

    2024年02月11日
    浏览(46)
  • SQL事务的开启,提交和回滚

    在处理数据库数据的时候会出现一种情况就是我们删除两个关联的表其中一个表的信息,另一个表也需要改动,但是我们SQL语句在同时更改两个表的同时,难免会出现一个表修改成功,另一个出现错误,这时候表与表之间就会出现矛盾,就用到了回滚,为了更安全的修改表的

    2024年02月04日
    浏览(60)
  • 律师使用ChatGPT 进行法律文献检索提交了错误信息;李开复表示,威力强大的大模型将彻底变革人工智能

    🚀 一名律师使用ChatGPT 进行法律文献检索提交了错误信息 近日,一名律师在法庭案件中使用聊天机器人 ChatGPT 进行法律文献检索,结果提交了错误信息, 揭示了人工智能在法律领域的潜在风险,包括误传错误信息。 该事件引发了法律界对于人工智能工具在法律研究中适当使

    2024年02月06日
    浏览(64)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包