事务补偿

两盒软妹~` 提交于 2019-12-04 20:58:21

微服务架构:最终一致性 + 事务补偿

分布式事务产生的原因

  • 数据库分库分表
  • 微服务化
  • 在微服务架构中,每个服务在用本地事务的时候,知道自己执行的事务是成功还是失败,但是无法知道其他服务节点的事务执行情况,因此需要引入协调者TM,负责协调参与者RM的行为,并最终决定这些参与者是否把事务进行提交。

随着微服务架构的流行,让分布式事务问题日益突出, 那么常见的分布式事务解决方案有哪些呢? 如何理解最终一致性和它的事务补偿机制呢?

刚性事务 - 强一致性

image.png

如上图,这是个标准的全局事务,事务管理器控制着全局事务,管理事务的生命周期,并通过XA协议与资源管理器协调资源;资源管理器负责控制和管理实际的资源 (这里的资源管理器,可以是一个DBMS,或者消息服务管理系统)

两阶段提交

它是XA用于在全局事务中协调多个资源的机制,常用于事务管理器资源管理器之间,解决一致性问题,分两阶段:

  • 提交事务请求
  • 执行事务请求

image.png

2PC的问题

  • 效率低,与本地事务相比,XA协议的系统开销比较大(数据被锁定的时间跨度整个事务,直到全局事务的结束),只有支持XA协议的资源才能参与分布式事务。
  • 2PC是反可伸缩模式的,在事务处理过程中,参与者需要一直持有资源直到整个事务的结束,这样当业务规模越来越大的情况下,它的局限性就越明显。
  • 数据不一致,在2pc中的第二阶段时,当TM向RM发送提交请求之后,发生局部的网络异常或者在发送提交请求过程中TM发生故障, 这会导致只有一部分RM收到了提交请求,然后没有收到提交请求的RM不会执行事务的提交,于是整个分布式系统便会出现数据不一致。
  • 单点故障, 由于TM的重要性,一旦发生故障,整个事务失效

3PC的改进

增加了超时机制, 主要解决单点故障问题,并减少资源锁定时间,一旦RM无法及时收到来至TM的信息之后,它会默认执行Commit操作, 而不会一直持有事务资源并处于阻塞状态。但是这种机制同样会导致数据不一致的问题,由于网络的原因,TM发送的回滚动作,没有被RM及时的收到,那么RM等待超时后就执行了提交操作,这样就和收到回滚操作并执行的RM之间存在了数据不一致的情况。

柔性事务 - 最终一致性

在2008年,eBay公布了基于BASE准则的最终一致性解决方案,它主要采用了消息队列来辅助实现事务控制流程,其核心通过消息队列的方式来异步执行分布式处理的任务,如果事务失败,则可以发起人工重试的纠正流程(比如对账系统,对处于dead letter queue的问题进行处理)

消息发送一致性

微服务架构下,需要通过网络进行通信,就自然引入了数据传输的不确定性,也就是CAP原理中的P-分区容错,而这里的消息发送一致性是可靠消息的保证。

生成消息的业务动作与消息发送的一致(e.g: 如果业务操作成功,那么由这个业务操作所产生的消息一定会成功投递出去,否则就丢失消息)

最终一致性.png

如上图,保证消息发送一致性的一般流程如下:

  • Producer先把消息发送给消息中间件服务,消息的状态标记为待确认,这个状态并不会被Consumer消费,对于长期待确认的消息,消息中间件会调用Producer的查询接口,查看最新状态,根据结果决定是否删除消息。
  • Producer执行完业务操作后,向消息中间件服务,发送确认消息
  • 这时消息的状态会被更改为待发送(可发送)
  • Consumer监听并接收待发送状态的消息,执行业务处理
  • Consumer业务处理后,向消息中间件服务发送ACK,确认消息已经收到(消息中间件服务将从队列中删除该消息)

消息的ACK确认流程中,任何一个环节都可能会出问题!

未ACK的消息,采用按规则重新投递的方式进行处理(很多MQ都提供at least once的投递,持久化和重试机制),一般还会设置重发的次数, 超过次数的消息会进入dead letter queue,等待人工干预或者延后定时处理。

业务接口的幂等性

消息的重复发送会导致业务接口出现重复调用的问题,主要原因就是消息没有及时收到ACK确认导致的, 那如何实现幂等性设计呢?

在实际的业务场景中, 业务接口的幂等性设计,常结合查询操作一起使用,

比如根据唯一标识查询消息是否被处理过, 或者根据消费日志表,来维护消息消费的记录。

保证最终一致性的模式

  • 可查询模式,任何一个服务操作都提供一个可查询接口,用来向外部输出操作执行的状态,下游Consumer可以通过接口得知服务操作执行的状态,然后根据不同的状态做不同的处理操作(执行或者取消), 该模式对业务接口有一定侵入性。
  • 补偿模式, 有了查询模式,我们能够知道操作的具体状态,如果处于不正常状态,我们可以修正操作中出现的问题,或许是重新执行,或许取消已经完成的操作,通过修复是的整个分布式系统达到最终一致。
  • 最大努力通知模式, 在调用支付宝交易接口或微信支付接口时,一般会在回调页面和接口里,解密参数,然后调用系统中更新交易状态相关的服务,将订单更新为付款成功。同时,只有当回调页面中输出了success字样或者标识业务处理成功相应状态码时,支付宝才会停止回调请求。否则,支付宝会每间隔一段时间后,再向客户方发起回调请求,直到输出成功标识为止。

 

简单聊聊消息队列的事务补偿机制

 

因为一直学习与尝试负责公司的推送相关业务,包括整个应用的实现,其中就采用了基于消息队列的异步事件驱动模型来做解耦异步处理,所以就要去做了解一些相关的知识点,这边稍作总结,并整理一下消息补偿机制的一套简单实现的代码设计图。

采用基于消息队列的异步事件驱动模型来解决问题的时候,一个计较棘手的问题就是事务的一致性。

案例:现在用户发起一个创建订单的请求,如果我们是单系统架构,那么修改订单表,修改库存表可能都是在同一个事务中完成,所以轻而易举就达到了事物一致性原则,但是这不是我们要讨论的,所以就带过。现在微服务架构在互联网公司大火特火,热度未减,分布式是事务也成为了一个亟待解决的问题,阿里云GTS标榜如何让分布式事务更简单。

比如,用户发起一个创建订单的请求,首先在订单服务上生成了新的订单,同时还要去库存服务中减去库存,因为是分布式架构,所以库存扣减与订单创建可能是在两个遥远的机器上,如果想要通过本地事务来解决那几乎是不可能的,保证两个事务之间的状态一致性——订单创建成功,库存扣减失败,如何回滚订单?一直都是分布式架构中绕不开的挑战。

分布式架构中如何解决事务问题,在很多技术群都上都在讨论,比如dubbo , spring cloud等等。目前还没有接触到这方面相关知识,后续如果有幸参与,可做分享,本次想要聊的是假基于消息队列的异步事件驱动是如何解决如上的分布式问题,以及如何保证事务一致性。

事务一致性原则(ACID):

  • Atomicity - 原子性,改变数据状态要么是一起完成,要么一起失败

  • Consistency - 一致性,数据的状态是完整一致的

  • Isolation - 隔离线,即使有并发事务,互相之间也不影响

  • Durability - 持久性, 一旦事务提交,不可撤销

订单创建完成之后,发送一个createOrderEvent到消息队列中,由消息队列负责转发给订阅该消息的消费者进行处理。

好,这个时候如果消息消费 成功,但是库存不足,库存扣减失败,订单创建则不能成功,这个时候很好处理,由库存服务推送一个subInventoryFail给到订单服务,订单服务根据消息将订单转为失败状态。

1、从用户体验的角度来说,整个过程是异步的,所以对于用户的体验来说,就做不到“立马成功或立马失败”的效果。

2、从技术的角度来说,整个过程你不再关注同一个事物的问题,而是关注最终订单的状态是否一致。【注:从分布式事务<-->最终一致性】保证事务最终一致性,但是基于这种事件驱动达到最终一致,解耦事务的成功实施需要依赖几个因素。

a、消息的投递是否可靠。

b、消息的可靠性,例如订单服务已经成功创建订单,但是还没来得及发送消息就宕机或者各种原因,导致订单的状态不一致。

基于以上两点的考虑,我们使用了一种基于本地事务的方案来保证消息最终的一致性。

创建订单与创建消息事件都在本地事务中,属于同一个事务,可以保证订单表与消息事件表的数据一致性。发送消息到消息中间件,在事务提交之后发送。到了库存服务的时候,启动一个定时任务去扫描消息事件表,将未投递失败/消费 失败的消息进行消费,即补偿事务一致性。

定时任务的方案可能不是最佳的,可以稍作改定,比如采用阿里巴巴开源的Canal

公司目前也是采用这种架构来解决订单与库存问题。有网友的做法是保证消息投递的可靠性,我们则是保证消费的一致性,具体的文章点我>>

可以将消息队列的进行封装,做成了一个starter,代码设计上大致如图下:

@山茶果

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!