构建基于RocketMQ的分布式事务服务

说在前面

Apache RocketMQ-4.3.0正式Release了事务消息的特性,顺着最近的这个热点。第一篇文章,就来聊一下在软件工程学上的长久的难题——分布式事务(Distributed Transaction)。数据库

这个技术也在各个诸如阿里,腾讯等大厂的内部,被普遍地实现,利用及优化。可是因为理论上就有难点,因此分布式事务就隐晦得成了大厂对于小厂的技术壁垒。相信来看这篇文章的同窗,必定都听过不少关于分布式事务的术语,比较二阶段提交,TCC,最终一致性等,因此这里也很少普及概念。bash

基于RocketMQ的分布式事务

咱们直接上正题,利用RocketMQ设计本身的分布式事务组件。网络

举个虚拟场景引出问题分布式

用户从农行转帐100元去招行 ,农行的系统和招行的系统分别部署在本身的机房,系统之间经过消息进行通讯,防止过分耦合。组件化

整个模型能够不恰当得描述为:农行扣了100元后,发送“已经扣款”的消息给招行,招行收到消息,知道农行扣款成功了,而后在招行帐户上加100元。性能

问题是,农行这边,方案1. 先扣100元再发消息,方案2. 先发消息再扣100元优化

整理下整个事务不一致的场景:this

方案1,spa

农行扣100后成功,可是消息发送失败,招行没有加100设计

方案2,

消息发送成功,可是农行扣100元失败,招行收到消息加了100

各位同窗应该已经发现问题所在了,扣款和发送消息这两个事情,没有办法经过调换顺序实现「同时成功」,或者「同时失败。若是前者成功,后者失败,就会形成不一致。

RocketMQ,如下简称RMQ,为了实现事务消息引入了一种新的消息类型:TransactionMsg

一个完整的事务消息分红两个部分:

HalfMsg(Prepare) + Commit/RollbackMsg

Producer发送了HalfMsg后,因为HalfMsg不是一个完整的事务消息,Consumer没法马上就消费到该消息,Producer能够对HalfMsg进行Commit或者Rollback来终结事务(EndTransacaction)。只有当Commit了HalfMsg后,Consumer才能消费到这条消息。RMQ会按期去向Producer询问,是否能够Commit或者Rollback那些因为错误没有被终结的HalfMsg来结束它们的生命周期,以达成事务最终的一致。

依然是刚刚的转帐场景,咱们用RMQ事务消息来优化下流程:

  1. 农行向RMQ同步发送HalfMsg,消息中携带农行即将要扣100元的信息

  2. 农行HalfMsg成功发送后,执行数据库本地事务,在本身的系统中扣100元

  3. 农行查看本地事务执行状况

  4. 本地事务返回成功,农行向RMQ提交(Commit)HalfMsg

  5. 招行系统订阅了RMQ,顺利收到农行已经扣款100元的信息

  6. 招行系统执行本地事务,在招行的系统中加100元

image

image

图1:RMQ事务消息原理

一样得,咱们逐个来分析下这个流程是否是会出现不一致:

  1. 农行发送HalfMsg是同步发送(Sync),若是HalfMsg发送不成功,压根就不会执行本地事务

  2. 发送HalfMsg成功,可是农行扣款****本地事务失败,也没事,若是本地事务没有成功,马上就发送Rollback去回滚HalfMsg。就当以前啥事都没有发生过

  3. 农行本地事务成功了,可是Commit却失败了,可是因为HalfMsg已经在RMQ中,RMQ就能经过定时程序让农行从新检测本地事务是否成功从新Commit。Rollback失败了也是同理

  4. 招行消费了消息后,加钱本地事务失败了,可是招行收到的消息持久化在MQ,甚至能够持久化在招行数据库,能够进行事务重试

刚刚讨论的案例是很是理想化的,整个分布式事务中,只涉及到了金额的变化,可是,真正的线上系统,做为消息发送方的本地事务可能就很是复杂,可能涉及到了几十张不一样的表,那RMQ用定时器来Check HalfMsg,难道去查下涉及该事务的每一张表的数据是否提交成功?显然这种方案很是业务侵入很是大,而且很难组件化。因此须要在本地事务中设计一张Transaction表,将业务表和Transaction绑定在同一个本地事务中,若是农行的扣款本地事务成功时,Transaction中应当已经记录该TransactionId的状态为「已完成」。当最后须要检查时,只须要检查对应的TransactionId的状态是不是「已完成」就好,而不用关心具体的业务数据。

再谈一个小细节,

细心的同窗可能发现,刚刚No.3的讨论实际上是有点不严谨的,RMQ在调用Commit或者Rollback时,用的是Oneway的方式,熟悉RMQ源码的话,知道这种网络调用是只单向发送Request,不会去获取Response消息发送性能上是有很是大的提高的,可是若是真的发送失败,Producer是不会知晓的,最后只能经过定时检查HalfMsg才能终结事务

public void endTransactionOneway(
        final String addr,
        final EndTransactionRequestHeader requestHeader,
        final String remark,
        final long timeoutMillis
    ) throws RemotingException, MQBrokerException, InterruptedException {
        RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, requestHeader);

        request.setRemark(remark);
        // 使用Oneway发送end transaction类型的
        this.remotingClient.invokeOneway(addr, request, timeoutMillis);
    }
复制代码

脱离RocketMQ的分布式事务

不是全部的MQ都能支持事务消息,如何使用通常的MQ来搭建分布式事务组件,甚至抽象成一个事务SOA服务?

其实仔细分析下RMQ的事务消息,咱们能够把它拆解成两个部分:

事务管理器 + 消息

所谓的事务管理器,就是对于事务的预备(Prepare)提交(Commit)回滚(Rollback)的管理,另外还包含预备事务的定时检查器

消息,指的就是通常的同步消息,发送后能明确获得发送结果,用于事务系统与业务系统解耦。几乎全部的分布式MQ都是支持这种消息的。

咱们来设计下本身的DistributedTransaction SOA,如下简称DT-SOA

image

image

图2:分布式事务服务化

流程仍是没有变,但分布式事务再也不强依赖RMQ,而是用通常的MQ代替:

  1. 系统A发送事务,首先调用DT-SOA的Prepare方法准备开启事务,因为是同步调用,获取SendResult,若是发送成功,拿到全局分布式事务的ID——TID

  2. 系统A用获取到的TID执行本地事务,本地事务中包含Transaction状态表,成功后将TID对应的状态置为“已完成”

  3. 系统A调用DT-SOA提交事务,DT-SOA用MQ发送同步消息给系统B

  4. 系统B监听对应Topic,接收到消息后,执行对应的本地事务

相关文章
相关标签/搜索