人的知识就好比一个圆圈,圆圈里面是已知的,圆圈外面是未知的。你知道得越多,圆圈也就越大,你不知道的也就越多。

0%

Kafka 事务消息

Kafka 中的事务,它解决的问题是,确保在⼀个事务中发送的多条消息,要么都成功,要么都失败。注意,这⾥⾯的多条消息不⼀定要在同⼀个主题和分区中,可以是发往多个主题和分区的消息。
Kafka 的这种事务机制,单独来使⽤的场景不多。更多的情况下被⽤来配合 Kafka 的幂等机制来实现 Kafka 的 Exactly Once 语义。
当然,我们可以在 Kafka 的事务执⾏过程中,加⼊本地事务,来实现分布式事务。(但是不同于 RocketMQ,Kafka 是没有事务反查机制的)

实现分布式事务

我们以订单和购物车这个例子来介绍 Kafka 是如何用消息队列来实现分布式事务。

Kafka事务应用示例

如上图所示:
首先,订单系统在消息队列上开启一个事务。
然后订单系统给消息服务器发送一个“半消息”,这个半消息不是说消息内容不完整,它包含的内容就是完整的消息内容,半消息和普通消息的唯一区别是,在事务提交之前,对于消费者来说,这个消息是不可见的。
半消息发送成功之后,订单系统就可以执行本地事务了,在订单库中创建一条订单记录,并提交订单库的数据库事务。然后根据本地事务的执行结果决定提交或者回滚事务消息。
如果订单创建成功,那就提交事务消息,购物车系统就可以消费到这条消息继续后续的流程。如果订单创建失败,那就回滚事务消息,购物车系统就不会收到这条消息,这样就基本实现了“要么都成功,要么都失败”的一致性要求。

在上面的实现过程中,有一个问题其实是没有解决的:如果在第四步提交事务消息时失败了怎么办?Kafka 的解决方案比较简单粗暴,直接抛出异常,让用户自行处理。我们可以在业务代码中反复重试提交,知道提交成功,或者删除之前创建的订单进行补偿。

事务消息实现机制

Kafka 是基于两阶段提交来实现事务的。

在 Kafka 事务中,有两个重要角色:事务协调者和事务日志主题。

  • 事务协调者
    负责在服务端协调整个事务。事务协调者并不是一个独立的进程,而是 Broker 进程的一部分,协调者和分区一样通过选举来保证自身的可用性。
  • 事务日志主题
    在 Kafka 集群中有一个特殊的用于记录事务日志的主题,这个事务日志主题的实例和普通的主题是一样的,里面记录的数据就是类似于“开启事务”、“提交事务”这样的事务日志。日志主题同样也包含了很多的分区。在 Kafka 集群中,可以存在多个协调者,每个协调者负责管理和使用事务日志中的几个分区。

Kafka 事务的实现流程:
Kafka事务实现流程

如上图所示,首先,当我们开启事务的时候,生产者会给协调者发一个请求开启事务,协调者在事务日志中记录下事务ID。
然后,生产者在发送消息之前,还要给协调者发送请求,告知发送的消息属于哪个主题和分区,这个信息也会被协调者记录在事务日志中。接下来,生产者就可以像发送普通消息一样来发送事务消息。需要注意的是,Kafka 在处理未提交的事务消息时,和普通消息是一样的,直接发给 Broker,保存在这些消息对应的分区中,Kafka 会在客户端的消费者中,暂时过滤未提交的事务消息。
消息发送完成之后,生产者给协调者发送提交或回滚事务的请求,由协调者来开始两阶段提交,完成事务。第一阶段,协调者把事务的状态设置为“预提交”,并写入事务日志。到这里,实际上事务已经成功了,无论接下来发生什么情况,事务最终都会被提交。之后便开始第二阶段,协调者在事务相关的素有分区中,都会写一条“事务结束”的特殊消息,当 Kafka 的消费者,也就是客户端,督导这个事务结束的特殊消息之后,它就可以把之前暂时过滤的那些未提交的事务消息,放行给业务代码进行消费了。
最后,协调者记录最后一条事务日志,标识这个事务已经结束了。

整个事务的实现流程时序图如下:
Kafka事务实现流程时序图

总结⼀下 Kafka 这个两阶段的流程:
准备阶段,⽣产者发消息给协调者开启事务,然后消息发送到每个分区上。
提交阶段,⽣产者发消息给协调者提交事务,协调者给每个分区发⼀条“事务结束”的消息,完成分布式事务提交。

小礼物走一走,来 Github 关注我