利用事务消息实现分布式事务

Monday, August 5, 2019

在关系型数据库中,我们已经接触过很多事务的实现了,那么在消息队列中为什么也需要事务呢。 消息队列的事务,主要解决的是消息生产者和消息消费者的数据一致性问题。

比如在电商系统中,订单系统处理订单后,需要清空已下单的购物车数据,因为购物车系统并不是支付流程的关键步骤,所以可以使用消息队列异步清理购物车。

file

这里主要涉及到两个步骤

  1. 在订单库中插入订单数据
  2. 发消息给消息队列,消息的内容就是刚刚创建的订单

购物车订购相应的主题,收到消息后,清空商品。因为两个系统并不是一起的,所以任何一个步骤都有可能失败,如果失败不做处理,就会造成数据不一致的问题,事务消息就是用来解决这种问题。

分布式事务

事务,简单来说,就是要么都成功,要么都失败。 一个严格的事务实现,要有四个属性(ACID)特性:

  • 原子性:事务操作不可分割,要么成功,要么失败。
  • 一致性:事务执行完之前,读到的数据一定是更新前的数据,反之。
  • 隔离性:一个事务的执行不会被其他操作干扰。
  • 持久性:一个事务的完成,后续的操作和故障不会对事务的结果产生影响。

在分布式系统中,完全实现上述四个特性,一般不太可能,会有所妥协。常见的分布式事务实现有2PC(二阶提交),TCC和事务消息。

事务消息适合需要异步更新数据,并且对数据实时性要求不高的场景,比如刚刚的例子。

消息队列的分布式事务实现

Kafka和RocketMQ都有提供相关的事务功能,先看看上面的例子如何用事务消息实现 file

  • 在消息队列开启事务,然后给消息服务器发送半消息(半消息是指,在事务完成之前,对消费者不可见)
  • 发送半消息成功后,执行本地事务,比如对数据库的写入,如果创建成功,则提交事务消息,失败则回滚事务消息。

这里有个问题,如果第四部中的提交事务失败该如何处理。

  • kafka比较粗暴,会直接抛出异常,让用户自行处理,我们可以尝试重复提交。

RocketMQ的分布式事务实现

RocketMQ中,增加了事务反查机制来解决上述问题,如果在提交的时候发生网络异常,RocketMQ的Broker没有收到提交或者回滚请求,Broker会定期去订单系统反查这个事务对应的本地事务的状态,然后根据结果来更新这个事务

为了支撑这个实现,我们需要在业务代码中加入一个反查本地事务状态的接口

比如上面的例子,我们只需要根据消息队列中订单的ID,去查订单库中是否存在这个订单即可

这种实现,并不依赖消息的发送方,即使发送事务消息的那个订单服务节点宕机了,RocketMQ依然可以通过其他订单服务的节点来执行反查,确保事务的完整性。

file

消息队列

如何确保消息不会丢失

主题和队列的区别