基于本地消息表实现分布式事务

221 阅读4分钟

大家好,我是小趴菜,在前面我们已经实战了springboot整合Atomitoc和Himly实现了分布式事务

今天我们看下如何基于本地消息表来实现分布式事务,首先我们看下整体的一个流程图是怎么样的

image.png

首先我们看下大致的流程是如何的

  • 1:员工基础信息服务保存员工的基础信息之外,还需要调用员工薪资服务员工职位服务
  • 2:员工基础信息保存之后,通过MQ将消息发送给员工薪资服务员工职位服务,继续执行后续的业务
  • 3:员工基础信息服务 本地保存一张消息表,保存的内容如下
   id:键
   business_id:业务id,要唯一,比如在这里可以员工的id
   message: {
         "员工薪资服务标识" : "发送中",   //记录状态,后续要修改
         "员工职位服务标识" : "发送中"
   }
   
  • 4:员工基础信息服务和保存消息是要在同一个事务下进行
  • 5:其它服务收到MQ消息之后,继续执行自己本地的业务,执行成功之后,需要发送ACK消息
  • 6:员工基础信息服务收到对应服务发送的ACK消息之后,然后修改该消息对应的发送状态,将状态从【发送中】 修改成 【已完成】

到此整个服务调用结束

现在我们看下在整个调用过程中会出现哪些问题

员工基础服务

员工基础服务是在本地事务中进行的,所以只要有异常,那么事务就会进行回滚,这时候员工的信息,本地消息表的数据就都不会保存

但是这时候可能MQ的消息是已经成功发送出去了的,并且员工薪资服务员工职位服务 都成功保存了记录,然后发送ACK的MQ消息回来,这时候员工基础服务收到消息发现本地并没有这条员工的记录信息,这时候其实是可以不处理这条消息的,为什么呢?

首先,要查询员工薪资和员工职位信息,那么肯定是要根据员工基础服务中的员工ID去查询的,这时候都没有这个员工信息,那么对于其它服务来说,这个本应该存在的员工信息就无效了,所以完全不用处理了

MQ消息发送失败

如果我们员工基础服务的本地事务提交成功了,员工基础数据和本地消息表都保存成功了,这时候发送MQ的消息失败了,也就是说员工薪资服务员工职位服务其中有一个或者两个服务都没有收到这个MQ消息,所以这时候就出现数据不一致了

对于使用RocketMq来说,其实可以使用事务消息来解决这个问题,如果其它MQ呢就需要其它手段解决了

就是通过一个第三方的定时任务,这个定时任务定时扫描我们的本地消息表,如果有消息状态一直处于【发送中】状态的消息,那么就要进行消息重发了,本地消息表是存了这条消息对应的一个服务标识的,所以可以将消息发送给对应的服务

消息ACK失败

比如这时候员工薪资服务本地数据保存成功,这时候需要发送ACK消息给 员工基础服务 修改本地消息表对应的发送状态,但是这时候ACK消息失败了,所以这条消息的发送状态就一直处于【发送中】

但是我们可以通过定时任务扫描,将这条消息进行重发,但是重发的话,员工薪资服务服务就做了两次保存的操作了,也就是幂等性问题

消息幂等问题

我们说到定时任务循环扫描本地消息表,如果有消息状态一直处于【发送中】,那么就要进行消息重发,消息重发就会导致一个幂等性问题,这时候可以怎么做呢?

  • 1:利用员工的ID做唯一索引,但是这时候还是需要返回ACK消息
  • 2:消息携带唯一标识,比如员工ID,如果处理过了就不处理这条消息,直接返回ACK即可