最近在项目中遇到一个分布式事务问题,跟平常的情况不太一样,特此记录一下。
正常事务执行情况
正常情况下一个分布式调用过程如下:
这种情况下的回滚:
如果方法C执行失败,需要回滚B;如果D执行失败,需要回滚B和C。
本地方法的回滚,可以抛异常回滚,也可以手动调spring的方法回滚。
远程方法的回滚,可以手动调提供方提供的回滚方法,也可以使用事务消息。
事务消息相对复杂一些,这里介绍一下事务消息的执行过程:
事务消息需要提供两个接口:
本地事务执行接口:根据事务执行情况返回3种状态:Commit、Rollback、UNKNOW。
本地事务状态查询接口:如果本地事务执行超时,或返回UNKNOW,MQ服务器调用该接口轮询事务状态。
本项目中的事务情况
上面介绍的是一个正常的分布式事务调用情况,但是在本项目中情况稍微复杂一点。原因是本地方法C存在嵌套调用情况,并且最底层的嵌套方法中引入了一个新的远程方法E的调用,同时嵌套的各层方法又被其他方法调用,散落在各个地方,使得方法E的回滚变得比较困难。
本项目中的情况:
这种情况下,在方法A、F、G、H中如果后续方法执行失败想要回滚方法E,会比较麻烦。原因是,像方法H这种直接调用方法C2的情况,回滚还稍微容易一点,因为跟方法E相关的上下文可能还保留的比较好,比较好回滚。但是像方法F这种调用方法C的情况,距离方法E的上下文比较远,想要回滚比较麻烦。
总结起来复杂的原因:
- 调用嵌套方法的地方很多,如果找不全容易出bug;
- 调用嵌套方法的地方距离远程方法上下文比较远,不容易获取回滚参数;
- 嵌套方法的参数和逻辑各不一样,想要通过嵌套方法判断是否要回滚,以及获取回滚参数需要做复杂的校验,增加了额外的逻辑和复杂度;
以上原因叠加起来,使得复杂度成倍增长。
有没有一种办法,尽量保证本地事务执行成功之后,再去执行远程方法,这样的话就不用回滚远程方法了?
除了使用上面介绍的事务消息,保证本地事务执行成功之后,消息接收方才会收到事务消息,执行远程方法。有没有不使用事务消息的方法?可以使用消息队列。
方案如下:
基本流程:
- 生产者发送上下文消息,消息内容包括远程调用参数、消息过期时间;
- 消费者收到消息,查询本地事务状态:
- 如果本地事务未完成,并且消息未过期,则转发消息给消息队列,过期时间不变;
- 如果本地事务未完成,并且消息已过期,则丢弃不再处理;
- 如果本地事务已完成,则调用远程方法。