分布式事务的解决方案

2019-08-08  本文已影响0人  华木公子

一 两阶段提交方案(XA事务)

第一阶段为准备阶段,由事务管理器向各个事务询问是否可执行,所有的参与者准备执行事务并锁住需要的资源,所有参与者向事务管理器汇报准备好,如果有一个未准备好就取消;
第二阶段为提交阶段。当事务管理器确认所有参与者都Ready 后,向所有参与者发送COMMIT 命令。

严重依赖数据库本身的事务XA实现,性能低下。

适合单模块跨多数据库的场景,不适合高并发的场景,也不适合多个系统之间的调用,在现在分布式微服务潮流下,已很少使用。

二 TCC方案(业务逻辑补偿事务)

  • 第一步 Try: 首先在业务逻辑代码中,进行预先操作,比如连接数据库,冻结需要扣减的资金,冻结库存数据等等;目的是为了确保在下一步数据操作是成功的;(资金冻结,库存冻结可能都不在本系统中,而是其他系统接口)
  • 第二步 Confirm:进行实际的业务逻辑操作,进行真正的资金扣减,库存扣减等逻辑;(资金扣减,库存扣减可能都不在本系统中,而是其他系统接口)
  • 第三步 Cancel:如果上述步骤出现了异常,那么就执行回退动作,回退也是通过业务逻辑补偿,比如回填资金,回填库存等(资金回填,库存回填可能都不在本系统中,而是其他系统接口);
  • 与业务逻辑耦合非常紧密;
  • 每个事务的处理代码非常复杂;

适用在事务粒度非常细,而且业务数据要求非常严格的情况下,比如和钱相关的转账等。大多情况下不适用,因为和业务逻辑耦合非常紧密,需要开发人员自己写代码来补偿,补偿代码会非常之复杂且容易出错。除非必要,不建议适用。

三 本地消息表方案(ebay的MQ事务方案)

  1. 系统A在本地数据库增加要给消息表;
  2. 执行本地事务后,往本地消息表插入一条消息记录;记录有状态(待消费,已消费),同时在zk中设置节点并监听;
  3. 本地消息表发送消息到MQ;
  4. 系统B从MQ消费消息,消费时,先插入系统B本地消息表,然后执行系统B本地事务。
  5. 系统B删除zk中对应节点,触发了A系统的监听;
  6. 系统A修改消息表中对应消息状态;
  7. 若系统B执行事务失败或者事务执行太久或者未收到消息,则:
  • 系统A开启一个定时轮询,对在一定时间内消息表中状态未改变的消息进行重发操作;
  • 系统B需要做好消息处理接口幂等性;
capture_20190729145426349.jpg

因为涉及到数据库消息表的读写,所以在性能上有不足,在高并发场景下不大适用。

三 可靠消息最终一致性方案(类似rocketMQ事务方案)

  1. 系统A先发送prepare消息到MQ(若消息发送失败,则整体失败,这里主要是确保MQ可用);
    2.系统A执行本地事务,事务成功告诉MQ确认消息,事务失败告诉MQ回滚消息;MQ收到确认消息,则触发系统B执行本地事务。
  2. 系统B执行本地事务;(如果本地事务执行失败,那么就重复执行,直到成功。也可以系统B进行回滚,然后通过其他途径如zk告知A系统,让A系统重发消息或者进行回滚,也可以记录下来进行人工补偿)。
  3. 针对prepare消息,rocketmq3.2.6版本之前会定期轮询,然后回调系统A接口,让系统A反馈这个消息因为事务已经回滚/失败了还是成功了,如果是成功了,就进行消息确认,如果失败/回滚了,就进行消息回滚操作。rocketmq3.2.6之后版本无此功能。
capture_20190729145426349.jpg

这是国内大多数互联网公司的使用的解决方案,如果不用rocketMQ事务方案,那么可以自己基于activitiMQ或者rabbitMQ封装来实现。



综上:

个人分布式事务的见解,事务设计立足于分布式,即各个系统只尽量保证自己系统事务是正常的,而不会去干涉别的系统的事务是否正常,以及异常之后的处理。所以,XA事务和TCC事务是和这个理念是违背的,因为它们都倾向于当前系统控制其他系统的事务异常。 而基于MQ的分布式事务,则更符合分布式事务的理念,即各系统只负责自己系统的事务正确,然后通过消息调用别的系统的事务,如果别的系统事务失败或者没有执行,那么也是想办法监听到这个结果,然后再次发送消息来触发其他系统的事务(因此,各系统需要做好消息接口幂等性设计)。

上一篇下一篇

猜你喜欢

热点阅读