加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 移动互联 > 正文

纯技术干货分享:分布式事务处理方式总结

发布时间:2019-07-30 20:16:45 所属栏目:移动互联 来源:IT技术分享
导读:在项目开发中,经常会需要处理分布式事务。例如数据库分库分表之后,原来在一个单库上的操作可能会跨越多个数据库。系统服务化拆分之后,原来的在一个系统上的操作可能会跨越多个系统。就连我们平时经常使用到的缓存(如redis、memcache等)也可能涉及分布式

事务补偿这种方式保证的是事务的最终一致性,即如果发生意外,会存在一个时间窗口(例如2S),在这个窗口内DB和缓存间是不一致的,但能保证最终两者的数据是一致的。至于定时任务周期的设定,要结合业务对“脏”数据的敏感程度以及系统的负载。

事务型消息

对于一个金融系统,假设有一个需求是用户注册成功后自动为用户创建一个账户。客户的信息维护在客户中心系统,客户的账户信息维护的账务中心系统,如果用户注册成功,必须保证客户的账户在账务系统创建成功。这显然也是一个分布式事务问题。

处理这个问题,显然也可以采用上一小节介绍的事务补偿机制来处理。但注册和开户并不要求一定是同步完成,且需要感知用户注册成功事件的系统并不只有账务系统一个(例如营销系统可能也需要感知用户注册成功的事件,给用户发优惠券),所以使用消息机制异步通知更加合适。那么问题就变成了“如果用户注册成功,一定要保证消息发送成功”。

应对这种场景,可以使用事务型消息。但前提条件是使用的MQ中间件必须支持事务型消息,比如阿里的RocketMQ。目前市面上其它一些主流的MQ中间件都不支持事务型消息,比如Kafka和RabbitMQ都不支持。

下面的序列图是事务型消息的执行流程:

纯技术干货分享:分布式事务处理方式总结
  • 相比于普通消息,发布者发送消息后,MQ并不是马上将消息发送给订阅者,而仅仅是将消息持久化存储下来。
  • 发送消息成功之后,发布者执行本地事务。例如我们例子中提到的用户注册。
  • 根据本地事务执行是否成功,发布者决定对之前已经发送的消息是commit还是rollback。如果是rollback,MQ会删除之前存储的消息。假设我们这里发送commit。
  • MQ接收到发布者发送的commit后,才会将消息发送给订阅者。之后,就可以利用MQ的消息可靠传输特性促使订阅者完成剩余事务操作,例如上面例子中提到的开户操作。

细心的小伙伴会发现,如果在上图中的第5步发生问题导致发送commit失败,不还是会导致消息发布者和消息订阅者间事务的不一致吗?为了防止这种情况的发生,增加MQ超时回调机制。

下面的序列图是事务型消息commit失败时的执行流程:

纯技术干货分享:分布式事务处理方式总结

当MQ长时间收不到发布者的commit/rollback通知时,MQ会回调发布者应用询问本地事务是否执行成功,是commit还是rollback之前的消息。发布者需要提供对应的callback,在callback中判断本地事务是否执行成功。

TCC两阶段提交

在某些场景下,一个分布式事务可能会涉及到多个参与者,且每个参与者需要根据自己当时的状态对事务进行响应。

假设这样一个场景,一个电商网站可以允许用户在支付时选择多种支付方式。例如总共需要支付100元钱,用户可以选择积分支付10元,账户余额支付90元。用户的积分由营销系统负责,账户余额由账务系统负责,订单的状态管理由订单系统负责。

  • 首先,要先确保事务的各个参与者满足条件才能执行事务。例如积分系统要确保用户的积分超过10元钱,账务系统要确保用户的账户余额大于90元钱才能发起这次交易。
  • 其次,就是要满足事务的原子性。这里的用户积分、用户余额、订单状态,要嘛全部处理成功,要嘛全部保持不变。

应对这种分布式事务场景,可以采用TCC两阶段提交的方式进行处理。

TCC将整个事务分成两个阶段——try和commit/cancel。TCC整个流程具有三种角色——事务发起者、事务参与者、事务协调者。以上面的订单支付为例,采用TCC实现处理事务的流程如下:

纯技术干货分享:分布式事务处理方式总结
  • 第一阶段try,订单系统分别调用promotion和account两个系统,询问该用户是否有足够的积分和账户余额。为了防止资源争抢,在这个阶段会对资源进行锁定,即营销系统会锁住用户的10元积分,账务系统会锁住用户的90元账户余额。
  • 如果在try阶段有任何一个参与者处理失败(例如用户积分不够10元或者用户的余额不够90元),则事务发起方(订单系统)会通知事务协调组件,后者会通知所有的事务参与者cancel在try阶段锁定的资源。
  • 如果在try阶段所有的参与者都处理成功,则事务发起方通知协调者commit这个事务,协调者会通知所有的参与者完成事务的commit。这时系统会完成真正的余额和积分扣减。2.2步是假设订单系统也要更新订单的状态。

但仅是这样处理还是有一致性问题,例如在第二阶段commit时如果发生宕机、网络抖动等异常情况,就可能导致事务处于“非最终一致”状态(参与者只执行了try阶段,没有执行第二阶段。或部分参与者第二阶段commit成功,部分参与者commit失败)。为了应对这种情况,需要增加事务日志,以便发生异常时回复事务。

可以利用DB这种可靠存储来记录事务日志。日志中应包含事务执行过程中的上下文、事务执行状态、事务的参与者等信息。事务日志可以由事务发起发负责记录,也可以交由事务协调方进行记录。

事务日志可以由主事务记录日志和从事务记录日志组成:

  • 主事务记录日志 用于记录事务发起方信息以及事务执行的整体状态。
  • 从事务记录日志 用于记录所有的事务参与者信息,以及每个参与者所属的从事务的执行状态。与主事务记录日志是一对多的关系。

有了事务日志后,就可以周期性的不断扫描事务日志,找到异常中断的事务。根据事务日志中记录的信息,推动剩余的参与者commit或者cancel,以便使整个分布式事务达到“最终一致性”。

下面是commit阶段发生异常时的事务补偿逻辑:

纯技术干货分享:分布式事务处理方式总结

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读