RocketMQ下的事务消息

目的:首先要明确事务消息要解决的问题是什么

    错误理解:事务消息是业务级别的事务,A生产消息后,BCD都消费成功后才算事务完成,如果BCD其中任一未成功完成消费逻辑,即算事务执行失败,需要回滚。其实这个理解不能算是错误理解,理想情况下的分布式事务消息就应该是这样的。但是现实决定了要达到这一目的的成本是及其高昂的,为了满足这个要求而引入的bug很可能是比其解决的问题要多得多。并且,真实情况下,消费失败的情况是很少的。没有必要为了解决消费失败的问题而引入如此高的复杂度。

    正确理解:事务消息解决的问题是当采用分布式的服务方案时,如果不采用事务消息,那么为了解决其事务问题是会付出较高的RT成本。

    例如下面这种情况,Bob向Smith转账:

   

    从上图可以发现,总耗时增长了一倍。这还是网络情况良好的情况,如果其中哪个RPC环节出现了延迟,总时间就会更长。或者说如果需要执行的RPC调用更多的情况下,RT也会成倍增长。

    用户体验差不说,转账成功率也会降低。

    那么这个时候应该用什么思路去解决这个问题呢?上图集群环境下的转账事务本质上还是一个整体的大事务,我们将这个大事务分割成两个(或若干个)小事务,让他们几乎同时开始。具体如下图所示:

    (注:这里只是分成2个事务的话,效果还不是很明显,当分解的数量较多时,缩短RT的效果就很明显)

    

    但是这个时候就引入了一个新的问题,什么时候发送消息?我们有2个选择

    1.Bob事务单元执行前发送消息

    2.Bob事务单元执行完成后发送消息

    对于选择1,存在的问题是,如果消息发送了,Smith也消费了这条消息,但是Bob扣款失败了,怎么办?岂不是Smith凭空多出100块?选择2也拥有类似的问题,可能Smith凭空损失100块,Bob没有收到这个100块。

    那么这个时候怎么解决这个问题呢?有一个很简单的解决方法就是我们把发消息这个操作放在Bob的事务单元里面去不就好了么~ 这个方法的确可以解决问题,但是会引入另外一个比较严重的问题,发消息是一个网络操作,网络操作相对于本地操作来说时间都是相对较长的,把这么一个比较耗时的操作放在DB的事务中,这样合适么?会不会引起数据库性能下降呢?这样是不是就得不偿失了呢?

    这个时候,就需要我们的事务消息出场了,它既可以异步解耦,又可以保证消息的事务性,这里主要讲一下RocketMQ所实现的两步提交方案,即2PC方案。流程图阿里云RocketMQ官网就有,直接搬运如下:

    结合我们举的转账的例子就是这样:

    图二的①对应图一的①,图二的②对应图一的⑦,图二的③对应的就是最后的Commit: 投递消息。图二的④可以不用去管它。

    按照这样的架构去实现,就可以较好的解决之前提到的多集群下事务逻辑RT过长的问题。事务消息即保证了消息的事务性,又可以让其消费逻辑几乎同时开始处理,这样也就保证RT不会过高。

再多想一点:

    但是如果消费者消费失败了怎么办?按照理想情况,如果消费者消费失败,那么这个集群中所有的事务都需要回滚,不管是消息发送者还是其他的消息消费者,都需要回滚。但是这样带来的成本及潜在的bug就太大太多了。如果把一个消息中渐渐设计到这种程度,那么可以说是过度设计了。文章开头也说过,毕竟真实情况下,哪里会有这么多的消费失败呢?

参考文献:https://www.jianshu.com/p/453c6e7ff81c