一、第一种方案:能不用分布式事务就不用

明确系统是否真的需要分布式事务;

因为不论任何一种分布式解决方案都会增加你系统的复杂度,这样的成本还是挺高的,千万不要因为追求某些设计,而引入不必要的成本和复杂度。

二、第二种方案:XA 分布式事务 (MySQL是支持XA事务的)

属于2PC;
XA是由X/Open组织提出的分布式事务的规范。

X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 是X/Open 这个组织定义的一套分布式事务的标准,也就是了定义了规范和API接口,由这个厂商进行具体的实现。这个思想在java 平台里面到处都是。
X/Open DTP 定义了三个组件: AP,TM,RM
AP(Application Program):也就是应用程序,可以理解为使用DTP的程序
RM(Resource Manager):资源管理器,这里可以理解为一个DBMS系统,或者消息服务器管理系统,应用程序通过资源管理器对资源进行控制。资源必须实现XA定义的接口
TM(Transaction Manager):事务管理器,负责协调和管理事务,提供给AP应用程序编程接口以及管理资源管理器
其中,AP 可以和TM 以及 RM 通信,TM 和 RM 互相之间可以通信,DTP模型里面定义了XA接口,TM 和 RM 通过XA接口进行双向通信,例如:TM通知RM提交事务或者回滚事务,RM把提交结果通知给TM。AP和RM之间则通过RM提供的Native API 进行资源控制,这个没有进行约API和规范,各个厂商自己实现自己的资源控制,比如Oracle自己的数据库驱动程序。

 

【分布式事务】分布式事务解决方案_其他

【分布式事务】分布式事务解决方案_其他_02

 

 

三、第三种方案:TCC 方案
属于2PC;

TCC 模型是把锁的粒度完全交给业务处理,它需要每个子事务业务都实现Try-Confirm / Cancel 接口。

Try:
  尝试执行业务
  完成所有业务检查(一致性)
  预留必须业务资源(准隔离性)
Confirm:
  确认执行业务;
  真正执行业务,不作任何业务检查
  只使用Try阶段预留的业务资源
  Confirm 操作满足幂等性
Cancel:
  取消执行业务
  释放Try阶段预留的业务资源
  Cancel操作满足幂等性

这三个阶段,都会按本地事务的方式执行。不同于 XA的prepare ,TCC 无需将 XA 的投票期间的所有资源挂起,因此极大的提高了吞吐量。
【分布式事务】分布式事务解决方案_其他_03

 

四、第四种方案:本地消息表+MQ

【分布式事务】分布式事务解决方案_其他_04

执行步骤:

  1. A 系统在自己本地一个事务里操作同时,插入一条数据到消息表;
  2. 接着 A 系统将这个消息发送到 MQ 中去;
  3. B 系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息;
  4. B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态;
  5. 如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理;
  6. 这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。

注意事项:

1.严重依赖于数据库的消息表来管理事务,高并发场景下,mysql会是个瓶颈 

2.本地消息队列是 BASE 理论,是最终一致模型,适用于对一致性要求不高的。实现这个模型时需要注意重试的幂等

五、第五种方案:可靠消息最终一致性方案

抛弃本地的消息表,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。
消息队列 RocketMQ 事务消息交互流程如下所示:

 

【分布式事务】分布式事务解决方案_其他_05

其中:

  1. 发送方向消息队列 RocketMQ 服务端发送消息。
  2. 服务端将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息。
  3. 发送方开始执行本地事务逻辑。
  4. 发送方根据本地事务执行结果向服务端提交二次确认(Commit 或是 Rollback),服务端收到 Commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;服务端收到 Rollback 状态则删除半消息,订阅方将不会接受该消息。
  5. 在断网或者是应用重启的特殊情况下,上述步骤 4 提交的二次确认最终未到达服务端,经过固定时间后服务端将对该消息发起消息回查。
  6. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  7. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤 4 对半消息进行操作。

说明:事务消息发送对应步骤 1、2、3、4,事务消息回查对应步骤 5、6、7。 

六、第六种方案:最大努力通知方案

这个方案的大致意思就是:

1.系统 A 本地事务执行完之后,发送个消息到 MQ;
2.这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
3.要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。

其实,RocketMQ 的消息重试,符合这个解释

你投入得越多,就能得到越多得价值