说到分布式事务,大家并不陌生。之前我已做过相关的总结(连接附本文后面),不过比较偏理论。在实际工作中,用得比较多的还是柔性分布式事务,今天主要把在工作中运用到的几种柔性分布式事务的场景及实现方式做一个简单介绍,也可以看做是柔性分布式事务的一个演进过程。
一、调用方保证
这种方式适合业务内自己使用,当方法内的任务一个逻辑发生异常时,整个方法都异常,由调用方进行重试。该方法不太适合于外部系统的交互,否则,这就是把自己的命运交到别人的手里,是比较危险的做法。
在上述调用示意图中,内部应用APP1和APP2之间,APP1的方法method1调用APP2里的method2方法,method2里有分布式事务逻辑,当分布式事务中的逻辑异常后,method2方法都失败或抛出异常,method1收到返回值是失败或异常后,都需要重试调用method2,确保method2执行成功。method2本身则需要确保逻辑中已经成功的逻辑再次被调用时处理也要正确。
@DistributedTransaction
void method2(){
//write DB
//send msg
//RPC invoke
}
该方法不建议大家在工作中使用,仅从保证分布式事务的正确性看是可以使用的,但从Java的规范上看,这种方法属于用异常控制流程,并不是很规范。同时,如果分布式事务中的逻辑是写本地库,发消息或RPC远程调用,则一般不建议把事务和发消息或RPC调用放到事务方法内,避免大事务。
二、定时任务扫描业务表
该场景主要运用于流量小、业务场景较单一的场景,或是业务处于验证阶段,为了快速验证业务是否有价值阶段,直接用业务表来做任务表,避免建多张表。在用本地事务写完业务表后,事务正常提交即可。通过一个定时任务查询业务表的增量数据,在定时任务中处理其他业务逻辑。
@Transaction
void method1(){
//write DB1 Bueiness Table
//other business Logic
}
void method2(){
//查询DB1中的Bueiness Table,时间从上次任务开始执行的时间开始
//处理自身的业务逻辑
}
该方案一般作为过渡方案,最终业务量上来后,会升级到下面的本地任务表的方案。
三、本地任务表
这是比较典型的分布式事务的解决方案,即:在业务库中,同步建一个任务表。业务表和任务表在本地事务中同时写入,再由一个定时任务定时查询任务表,把任务读取到后根据业务逻辑要求进行处理。
业务表和任务表在一个数据库中,由数据库的一个事务控制器实现事务。在应用中,另起一个定时任务,由定时任务去查询任务表,把任务表中新进的任务抓取后执行该定时任务需要执行的业务逻辑。
@Transaction
void method1(){
//write DB1 Bueiness Table
//write DB1 Task Table
}
void method2(){
//定时查询DB1中的Task Table
//任务抓取后执行自身的业务逻辑
}
该方案实际运用时,定时任务的稳定性需要我们特别关注,定时任务的稳定性决定我们该方案的可用性。建议把定时任务的执行情况监控起来,确保有问题时能第一时间处理,避免影响业务。
四、组件抽取
目前,对于Java语言开发的团队,其框架大部分都以Spring为主,故可基于SpringEvent异步事件做一个小的组件封装。主要思路为在事务中,发送Event异步事件,当异步事件发送成功则事务提交结束,当异步事件发送失败则异步任务落本地数据库后事务再提交结束,然后通过定时任务从任务表中抓取任务执行。
该方案以SpringEvent异步事件为主做了一个组件,当SpringEvent异步事件发送异常后,降级到本地任务表,确保异步任务的可靠性。即使没有封装为组件,在实际工作中,还是比较推荐大家使用该方案。
@Transaction
void method1(){
//业务数据写入DB成功
try{
//发送SpringEvent事件
}catch(Exception ex){
//写入本地任务表
}
}
void method2(){
//接收事件或定时任务的数据执行业务逻辑
}
当然,如果项目中没有使用Spring框架也没问题,有了上述的思想,可以根据自身使用的框架情况进行调整。正所谓“只要思想不滑坡,办法总比困难多”。
附:
刚性分布式事务
分布式事务,你了解多少?(上)_liaozxbj的博客-CSDN博客
柔性分布式事务