欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

为什么要有事务消息?
(last modified Feb 18, 2024, 10:09 PM )
by
侧边栏壁纸
  • 累计撰写 178 篇文章
  • 累计创建 62 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

为什么要有事务消息?

橙序员
2024-01-14 / 0 评论 / 0 点赞 / 264 阅读 / 1,834 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

事务消息是为了解决消息发送失败导致数据不一致的问题而提出的。在一个场景中,当我们创建订单时,需要通知库存系统和出货系统同时更新库存和出货消息。如果消息中间件挂掉或消息发送失败,订单系统、库存系统和出货系统的数据将无法保持一致性。为了解决这个问题,可以借助分布式事务的思路,将本地事务执行和消息发送看作两个分支事务,通过创建一个事务协调器来保证它们的原子性。在这个流程中,事务协调器作为消息中间件的一部分,能够保证订单系统在下单过程中生产的所有消息都在一个事务里。然而,事务协调器无法知道每个生产者会产生什么消息,只能将事务id绑定到生产者维度,导致只能保证一次发送多条事务消息的一致性。因此,基于这种实现的事务消息机制只能保证多条事务消息的一致性,但会扩大事务的范围,导致其他消息不能及时被消费者监听。需要注意的是,在这个场景中,订单的生产者还会发送其他消息,如通知用户充值、收货等消息,这些消息也会受到事务消息机制的限制。

场景分析

首先我们考虑如一个场景,在我们创建订单时,需要通知库存系统和出货系统,以同时更新库存和出货消息。它的流程是这样的:

  1. 订单系统下单,执行下单操作
  2. 下单完成后发送下单消息
  3. 库存系统接收下单消息,更新库存
  4. 出货系统接收下单消息,更新出货

这个时候,如果不假思索,那么我们整个下单流程如下:

image-20240113225429982

当然正常的系统,一直正常运行是不可能的。所以我们考虑一个故障场景:如果我们在发送消息时,消息中间件挂掉了该怎么办?

image-20240113225636996

这时候,我们会发现,由于中间件宕机,我们订单完成的消息丢失了,即使后续中间件恢复(或者我们由于网络原因消息根本没有发送成功),我们也无法保证订单系统,库存系统,出货系统的数据一致性,这个时候只能通过手动补偿的机制重新发送订单完成消息,但是给前台用户造成的体验是非常不好的!因为下单成功、变更库存、出货这三件事应该是原子的,要么全部成功,要么全部失败!

事务消息

针对上述消息可能发送失败的场景,我们需要保证本地事务执行和消息发送是原子的。如何实现原子性呢?

事务协调器实现

我们可以借助分布式事务的思路(AT模式),将本地事务执行和消息发送看作两个分支事务,这样的话我们可以创建一个事务协调器,具体流程改造如下:

  1. 订单系统创建事务ID,将事务ID发送到事务协调器中(全局事务开始)
  2. 订单系统下单,执行下单操作并发送下单消息(不可消费)
  3. 下单操作事务提交,通知事务协调器(全局事务执行)
  4. 事务协调器发送事务完成通知,下单消息变为可消费(全局事务提交)
  5. 库存系统接收可消费的下单消息,更新库存
  6. 出货系统接收可消费的下单消息,更新出货

image-20240113233053384

这个流程里,我们保证事务协调器是消息中间件的一部分,这样通过事务协调器,我们能够保证订单系统在下单过程中生产的所有消息都在一个事务里。但是这就会有一个问题,我们的事务协调器在设计的时候由于无法知道每个生产者会产生什么消息(Kafka没有消息查询能力~),所以创建事务id的时候只能绑定到生产者维度,也就是这个生产者可以包含多个消息,这些消息共同参与同一个事务。

所以基于这个实现事务消息的kafka也只能保证一次发送多条事务消息的一致性,这个的问题在于它把我们的事务扩大化了,由于它绑定了生产者,所以我们订单系统如果通过同一个生产者发送了其他消息也会导致其消息无法被消费者及时监听!

例如我们订单的生产者不单会生产下单完成的消息,也会在下单前通知用户充值,下单后通知用户收货等消息,这个时候如果使用kafka事务,就会导致这些消息均无法被下游及时消费,这种情况下我们提示用户充值的消息就迟滞了,也是会引发其他业务问题的!

消息预提交实现

既然通过事务协调器实现的粒度太大,那么我们换种思路,由于事务是由订单系统的本地事务发起的,所以我们可以将这个本地事务作为全局事务,这样我们便可以做这么改造:

  1. 订单系统创建事务id,根据事务id发送一条下单的半事务消息(不可消费)
  2. 半事务消息发送成功后,订单系统执行下单操作
  3. 下单操作事务提交,通知消息中间件
  4. 消息中间件将对应的半事务消息标记可消费
  5. 库存系统接收可消费的下单消息,更新库存
  6. 出货系统接收可消费的下单消息,更新出货

image-20240114001312566

在这个流程里,我们通过对消息状态的更新,完成了消息的不可消费到可消费的转换。而RocketMQ正是基于消息查询能力,实现了这一消息级别的分布式事务机制。在官网的事务交互流程图中,我们还可以看到其也可以通过消息回查来保证消息和本地事务的状态一致性:

事务消息

总结

我们通过上述讨论,了解了事务消息的产生背景:保证本地事务和消息发送的原子性。也通过探讨KAFKA和RocketMQ的事务机制,最终确定了两者不同的使用场景:

  • Kafka 的事务是以生产者为单位的,一个生产者可以包含多个消息,这些消息共同参与同一个事务。所以kafka可以说不支持事务消息,只支持生产者事务。
  • RocketMQ 的事务是以消息为单位的,每条消息都有自己的事务状态。这样可以实现更细粒度的事务控制。

所以我们如果开发中有这种消息强一致的场景,最好使用RocketMQ,而且RocketMQ支持消息查询,同步从节点也是消息维度进行同步的,比高低水位同步的Kafka一致性要好得多。(但是这种往往不是业务研发可以选型的,都严重依赖基础运维的提供了哪些消息中间件能力)

在上述case中,我们只讲述了本地事务执行与消息通知的原子性,并没有讨论库存系统和出货系统中的子事务回滚时的数据一致性问题,整体的分布式事务不在本篇文章之内,可参考如何保证分布式系统的一致性一文。

参考文章:

云消息队列 RocketMQ 5.x系列-开发参考-功能与特性-事务消息

Kafka 事务的实现原理

消息队列之事务消息,RocketMQ 和 Kafka 是如何做的?

0

评论区