分布式最终一致性事务

一、强一致性事务的瓶颈

  在《分布式强一致性事务》一文中介绍了分布式事务的常用协议2PC二阶段提交,虽然2PC能在很大程度上实现分布式事务中各节点的ACID,但也存在同步阻塞问题,协调者单点故障,协调者因网络原因导致的通知不周或收不全参与者回复导致的异常等问题。

  同时,即使能稳定的使用二阶段提交实现分布式事务,但是2PC通信过程中产生的耗时是巨大的,类似淘宝网,下完一个订单后可能需要与计费中心,订单中心,入库,出库等很多子系统打交道,此过程中带来的开销是不能接受的,等到所有流程通知响应完再返回告知用户下单成功,这样的体验也是极差的。

  为此,我们会比较容易想到用MQ来代替这个过程,因为MQ具有异步发起,子操作并行以及解耦子系统的特性,可以极大的提高用户体验。

二、基于MQ的最终一致性

  通过借助MQ队列,处理完业务逻辑后发送消息给消费方,并确保消息是发送成功的,之后消费方消费MQ队列来处理业务逻辑,如果消费并处理成功,则结束,如果没有成功,则重试,直到成功。若重试成功,但业务处理失败,则由人工介入处理(但消费成功,处理失败的场景是极少见的)。

三、实现思路

  通过MQ异步可靠发送,幂等等策略来保证数据的最终一致性,会不可避免的对业务有一定的侵入。

  异步+可靠发送消费思路:

  Producer:需要在业务表所在的库额外建一个消息表,确认表。消息表记录消息发送状态。消息表和业务数据要在一个本地事务里提交。消息表可以比较简单,有消息id,消息对应执行的方法,创建时间,发送状态即可,当业务逻辑执行成功,即向消费方发送消息,并向消息表插入一条消息,状态为已发送。

  Consumer:从MQ队列消费完消息后,处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,那发送给生产方一个confirm消息,Producer会将这条消息插入确认表,状态为已确认,表明整个流程已经处理成功了。如果处理失败,可以考虑发送给Producer一个failed消息,Producer同样会将这条消息插入确认表,只是状态为确认失败。

  重试机制:Producer有一个定时任务,即扫描已发送成功但确认状态为失败的消息,当扫描到有失败的消息时,此时有两个选择, 一是根据这条消息对应的执行方法,去作发送方业务逻辑上的回滚,或通过执行对应的反向sql进行回滚。二是去重发这条失败,即Producer的重试机制,可以设置重试发送的次数,直到消费方确认成功,若到达重试次数仍未有确认成功的状态,则将该消息丢入死信队列,由人工介入重发或进行手工处理。

  注:由于每个节点都可能同时是Producer也是Consumer,所以使用到该方案的服务节点都应该在业务表所在库去创建消息表和确认表。

  幂等机制:在上面的描述中,业务的执行,消息的发送和插入消息表由于在一个本地事务中,所以保证了可靠发送。而消费方处理失败后,有发送方定时重发和私信队列人工处理,也保证了可靠消费(实际生产中,消费方消费后处理业务失败的场景极为少见)。这整个流程保证了可靠发送与可靠消费,但却保证不了一个问题,即消息可能重复发送或重复消费。

  比如定时任务设置的是五分钟,当扫描到一条已发送但未确认的消息时,会进行发送,如果消费方处理时间较长,五分钟内依旧得不到确认成功的答复,则可能扫描到该消息又进行了重发,这样重复发送又可能导致了消费方重复消费,在一个调用过程中,保证消息只被发送一次和只被消费一次,是至关重要的。

  此时引入幂等的概念就极其重要,可以在业务表所在库中增加一张幂等表。如在Producer节点的幂等表记录每条消息的发送次数,保证只能有一次发送,Consumer节点的幂等表记录每条消息的消费次数,保证只能有一次消费,因为每条消息都有一个唯一的消息id,所以可以以消息id来作为判断的依据。

  当Producer产生一条消息时,则将这条消息记录进幂等表,表示该条消息已发送。当消费方返回确认成功时,将消息插入确认表,状态为成功,此时整个流程完结。当返回确认失败时,除了要将消息插入确认表中,状态为失败外,还要将幂等表中该消息id对应的发送记录置为0,方便后面定时器根据发送记录进行重发。若对失败消息的处理选择的是逻辑上的回滚,则可以省去对幂等表的操作。

  当Consumer消费一条消息,并处理完业务逻辑时,则将这条消息记录进幂等表,表示该条消息已消费。当再次消费消息时,可前往幂等表进行判断,存在相同的消息id时,则可以将该消息丢掉,不再重复消费。

  以上描述仅提供参考,对于基于MQ的可靠时间,有许许多多的玩法,例如消费方消费消息,业务处理失败后,也向Producer发送确认成功的消息,自己把这条消息丢自己的死信队列去重试或人工干预,所以一个完整的可靠事件SDK需要提供不同方案对应的不同解决问题的流程。

四、事务消息

    事务消息是一个理想的方案,即消息的发送和消费是自带事务的,即我们只要把消息扔到MQ,那么这个消息肯定会被消费成功。

  生产方不用担心消息发送失败,不用担心消息丢失。而消费方如果消息处理失败了,还有机会继续消费,直到成功为止。

  事务消息可以少去我们很多为了保持一致性而对业务逻辑上的侵入,但遗憾的是市面上大部分MQ中间件都不支持事务消息。

  RocketMQ最新版本已经支持事务消息,但仍需经过时间的考验和成功产品的出现,才能保证基于MQ的最终一致性的春天到来,真正解决分布式事务这一分布式系统中最令人头疼的问题,没有之一。

posted @ 2018-08-12 17:13  纪煜楷  阅读(1400)  评论(0编辑  收藏  举报