前言

最近有个网友问了我一个问题:零碎中大事务问题要如何解决?

正好前段时间我在公司解决过这个问题,咱们过后因为我的项目初期工夫比拟缓和,为了疾速实现业务性能,疏忽了零碎局部性能问题。我的项目顺利上线后,专门抽了一个迭代的工夫去解决大事务问题,目前曾经优化实现,并且顺利上线。现给大家总结了一下,咱们过后应用的一些解决办法,以便大家被雷同问题困扰时,能够参考一下。

大事务引发的问题

在分享解决办法之前,先看看零碎中如果呈现大事务可能会引发哪些问题

从上图能够看出如果零碎中呈现大事务时,问题还不小,所以咱们在理论我的项目开发中应该尽量避免大事务的状况。如果咱们已有零碎中存在大事务问题,该如何解决呢?

解决办法

少用@Transactional注解

大家在理论我的项目开发中,咱们在业务办法加上@Transactional注解开启事务性能,这是十分广泛的做法,它被称为申明式事务。
局部代码如下:

@Transactional(rollbackFor=Exception.class)   public void save(User user) {         doSameThing...   }

然而,我要说的第一条是:少用@Transactional注解。
为什么?

咱们晓得@Transactional注解是通过spring的aop起作用的,然而如果使用不当,事务性能可能会生效。如果凑巧你经验不足,这种问题不太好排查。至于事务哪些状况下会生效,能够参考我之前写的《spring事务的这10种坑,你稍不留神可能就会踩中!!!》这篇文章。
@Transactional注解个别加在某个业务办法上,会导致整个业务办法都在同一个事务中,粒度太粗,不好管制事务范畴,是呈现大事务问题的最常见的起因。

那咱们该怎么办呢?

能够应用编程式事务,在spring我的项目中应用TransactionTemplate类的对象,手动执行事务。
局部代码如下:

   @Autowired   private TransactionTemplate transactionTemplate;      ...      public void save(final User user) {         transactionTemplate.execute((status) => {            doSameThing...            return Boolean.TRUE;         })   }

从下面的代码中能够看出,应用TransactionTemplate的编程式事务性能本人灵便管制事务的范畴,是防止大事务问题的首选方法。

当然,我说少应用@Transactional注解开启事务,并不是说肯定不能用它,如果我的项目中有些业务逻辑比较简单,而且不常常变动,应用@Transactional注解开启事务开启事务也不妨,因为它更简略,开发效率更高,然而千万要小心事务生效的问题。

将查问(select)办法放到事务外

如果呈现大事务,能够将查问(select)办法放到事务外,也是比拟罕用的做法,因为个别状况下这类办法是不须要事务的。

比方呈现如下代码:

@Transactional(rollbackFor=Exception.class)   public void save(User user) {         queryData1();         queryData2();         addData1();         updateData2();   }

能够将queryData1和queryData2两个查询方法放在事务外执行,将真正须要事务执行的代码才放到事务中,比方:addData1和updateData2办法,这样就能无效的缩小事务的粒度。
如果应用TransactionTemplate的编程式事务这里就十分好批改。

   @Autowired   private TransactionTemplate transactionTemplate;      ...      public void save(final User user) {         queryData1();         queryData2();         transactionTemplate.execute((status) => {            addData1();            updateData2();            return Boolean.TRUE;         })   }

然而如果你切实还是想用@Transactional注解,该怎么拆分呢?

public void save(User user) {         queryData1();         queryData2();         doSave();    }       @Transactional(rollbackFor=Exception.class)    public void doSave(User user) {       addData1();       updateData2();    }

这个例子是十分经典的谬误,这种间接办法调用的做法事务不会失效,给正在坑中的敌人提个醒。因为@Transactional注解的申明式事务是通过spring aop起作用的,而spring aop须要生成代理对象,间接办法调用应用的还是原始对象,所以事务不会失效。

有没有方法解决这个问题呢?

1.新加一个Service办法

这个办法非常简单,只须要新加一个Service办法,把@Transactional注解加到新Service办法上,把须要事务执行的代码移到新办法中。具体代码如下:

@Servcie  publicclass ServiceA {     @Autowired     prvate ServiceB serviceB;       public void save(User user) {           queryData1();           queryData2();           serviceB.doSave(user);     }   }      @Servcie   publicclass ServiceB {         @Transactional(rollbackFor=Exception.class)      public void doSave(User user) {         addData1();         updateData2();      }      }

2.在该Service类中注入本人

如果不想再新加一个Service类,在该Service类中注入本人也是一种抉择。具体代码如下:

@Servcie  publicclass ServiceA {     @Autowired     prvate ServiceA serviceA;       public void save(User user) {           queryData1();           queryData2();           serviceA.doSave(user);     }          @Transactional(rollbackFor=Exception.class)     public void doSave(User user) {         addData1();         updateData2();      }   }

可能有些人可能会有这样的疑难:这种做法会不会呈现循环依赖问题?

其实spring ioc外部的三级缓存保障了它,不会呈现循环依赖问题。如果你想进一步理解循环依赖问题,能够看看我之前文章《spring解决循环依赖为什么要用三级缓存?》。

3.在该Service类中应用AopContext.currentProxy()获取代理对象

下面的办法2的确能够解决问题,然而代码看起来并不直观,还能够通过在该Service类中应用AOPProxy获取代理对象,实现雷同的性能。具体代码如下:

@Servcie  publicclass ServiceA {       public void save(User user) {           queryData1();           queryData2();           ((ServiceA)AopContext.currentProxy()).doSave(user);     }          @Transactional(rollbackFor=Exception.class)     public void doSave(User user) {         addData1();         updateData2();      }   }

事务中防止近程调用

咱们在接口中调用其余零碎的接口是不能防止的,因为网络不稳固,这种近程调的响应工夫可能比拟长,如果近程调用的代码放在某个事物中,这个事物就可能是大事务。当然,近程调用不仅仅是指调用接口,还有包含:发MQ音讯,或者连贯redis、mongodb保留数据等。

@Transactional(rollbackFor=Exception.class)   public void save(User user) {         callRemoteApi();         addData1();   }

近程调用的代码可能耗时较长,切记肯定要放在事务之外。

   @Autowired   private TransactionTemplate transactionTemplate;      ...      public void save(final User user) {         callRemoteApi();         transactionTemplate.execute((status) => {            addData1();            return Boolean.TRUE;         })   }

有些敌人可能会问,近程调用的代码不放在事务中如何保证数据一致性呢?这就须要建设:重试+弥补机制,达到数据最终一致性了。

事务中防止一次性解决太多数据

如果一个事务中须要解决的数据太多,也会造成大事务问题。比方为了操作不便,你可能会一次批量更新1000条数据,这样会导致大量数据锁期待,特地在高并发的零碎中问题尤为显著。

解决办法是分页解决,1000条数据,分50页,一次只解决20条数据,这样能够大大减少大事务的呈现。

非事务执行

在应用事务之前,咱们都应该思考一下,是不是所有的数据库操作都须要在事务中执行?

   @Autowired   private TransactionTemplate transactionTemplate;      ...      public void save(final User user) {         transactionTemplate.execute((status) => {            addData();            addLog();            updateCount();            return Boolean.TRUE;         })   }

下面的例子中,其实addLog减少操作日志办法 和 updateCount更新统计数量办法,是能够不在事务中执行的,因为操作日志和统计数量这种业务容许大量数据不统一的状况。

   @Autowired   private TransactionTemplate transactionTemplate;      ...      public void save(final User user) {         transactionTemplate.execute((status) => {            addData();                       return Boolean.TRUE;         })         addLog();         updateCount();   }

当然大事务中要甄别出哪些办法能够非事务执行,其实没那么容易,须要对整个业务梳理一遍,能力找出最正当的答案。

异步解决

还有一点也十分重要,是不是事务中的所有办法都须要同步执行?咱们都晓得,办法同步执行须要期待办法返回,如果一个事务中同步执行的办法太多了,势必会造成等待时间过长,呈现大事务问题。

看看上面这个列子:

   @Autowired   private TransactionTemplate transactionTemplate;      ...      public void save(final User user) {         transactionTemplate.execute((status) => {            order();            delivery();            return Boolean.TRUE;         })   }

order办法用于下单,delivery办法用于发货,是不是下单后就肯定要马上发货呢?

答案是否定的。

这里发货性能其实能够走mq异步解决逻辑。

   @Autowired   private TransactionTemplate transactionTemplate;      ...      public void save(final User user) {         transactionTemplate.execute((status) => {            order();            return Boolean.TRUE;         })         sendMq();   }

总结

自己从网友的一个问题登程,联合本人理论的工作教训分享了解决大事务的6种方法:

  • 少用@Transactional注解
  • 将查问(select)办法放到事务外
  • 事务中防止近程调用
  • 事务中防止一次性解决太多数据
  • 非事务执行
  • 异步解决

最初说一句(求关注,不要白嫖我)

如果这篇文章对您有所帮忙,或者有所启发的话,帮忙扫描下发二维码关注一下,或者点赞、转发、在看。在公众号中回复:面试、代码神器、开发手册、工夫治理有超赞的粉丝福利,另外回复:加群,能够跟很多大厂的前辈交换和学习。

本文由博客群发一文多发等经营工具平台 OpenWrite 公布