本地消息表实现方案

分布式系统中要实现强一致性比较困难,往往很多业务场景不要求强一致性,允许有个临时的业务中间状态。因此就可以采用最终一致性的分布式事务方案。

常用的分布式解决方案有实现XA事务的Atomikos,本地消息表方案,基于消息中间件的最终一致性方案,TCC方案,阿里的SEATA,SAGA方案和最大努力通知。下面主要对基于本地消息表实现最终一致性的分布式事务方案进行介绍。

本地消息表方案最初是ebay提出的,其实也是BASE理论的应用,属于可靠消息最终一致性的范畴。这里以支付服务和会计服务为例展开介绍本地消息表方案,大概流程是这样子:用户在支付服务完成了支付订单支付成功后,此时会调用会计服务的接口生成一条原始的会计凭证到数据库中,如图1所示。这里必须明确:支付服务处理完订单支付等逻辑后,此时若直接调用会计服务生成会计凭证数据的接口肯定会遇到分布式事务的问题。

因为用户完成支付后,此时得立马给用户一个支付的反馈,要做的就是提醒用户支付成功。因为会计服务生成的会计凭证保存到数据库的过程中可以对用户透明,用户也无需知道有这么一个流程,为了提高响应速度和解耦,因此可以引入mq来做到异步生成会计凭证,即用户完成支付订单支付后,此时可以将消息投递到mq中,然后会计服务再去监听mq消息去处理消费逻辑。如下:

引入mq后,同样他也有一些问题:

  1. 若支付服务完成支付逻辑后,在投递消息到mq中间件的过程中由于网络抖动等原因,没有投递到mq中导致消息丢失了怎么办?

  2. 会计服务在监听消息的过程中,由于网络原因没有接收到消息或消费过程中遇到异常,此时也会导致消息丢失,测试怎么办?

上述问题是因为传统的mq没有实现分布式事务,因此这里可以引入本地消息表结合mq的方式来解决分布式事务的问题,保证消息的可靠投递:

上图中的整体流程为:

  1. 在支付库中引入一张消息表来记录支付消息,即用户支付成功后同时往这张消息表插入一条支付成功的消息,状态为“发送中”。注意支付逻辑和插入消息表的代码要包裹在一个事务里面,这里保证了本地事务的强一致性。即支付逻辑和插入消息表的消息组成了一个强一致性的事务,要么同时成功,要么同时失败。

  2. 完成第一步的逻辑后,此时再向mq的PAY_QUEUE队列中投递一条支付消息,这条支付消息的内容跟保存在支付库消息表的消息内容一致。

  3. mq接收到消息后,此时会计服务也监听到这条消息了,此时会计服务处理消费逻辑即开始生成会计凭证。

  4. 会计凭证生成后,再反向向mq投递一条消费成功的消息到ACC_QUEUE队列。

  5. 同时支付服务又来监听这个会计服务消费成功的消息,当支付服务监听到这个消费成功的消息后,此时再将本地消息表的消息状态改为“已发送”。

  6. 经过前面5步后,整个业务就已经完成了。

但是他仍然有一些问题:

  1. 若消息在投递过程中丢失了,双方服务就会发生不一致

这时候,解决办法就是重复投递,同时消费者接口需要实现幂等性。此时引入一个消息恢复系统,他是一个定时任务,他会每隔一段时间去本地消息表中捞取状态为“发送中”的消息,然后重新投递到mq中间件中,然后消费者就会重新消费了。

若消费者已经消费过了,此时就不再处理消费业务逻辑,直接反向投递一条消费成功的消息到mq中,此时原来的生产者此时也会监听这条消费成功的消息,将本地消息表的消息状态改为“已发送”,此时消息恢复系统就不会再去捞取这条状态为“已发送”的消息,然后进行重新投递了。

上图中,会计服务返回的消息,也可以作为一种事件;他可以发送事件给统计服务,再执行统计服务事务操作,以达成一种事件驱动的事务方案,使用起来较为灵活。

本地消息表的方案的优点是建设成本比较低,其虽然实现了可靠消息的传递确保了分布式事务的最终一致性,其实它也有一些缺陷:

  1. 本地消息表与业务耦合在一起,难于做成通用性,不可独立伸缩。

  2. 本地消息表是基于数据库来做的,而数据库是要读写磁盘IO的,因此在高并发下是有性能瓶颈的。

参考

  • https://juejin.cn/post/6844904041659498509

最后更新于