面试官:Kafka 事务是如何工作的?

4,686 阅读5分钟

一、概述

我们先来回顾一下事务的概念:要么全部成功,要么全部失败! Kafka 事务也是一样的。

Kafka 0.11.0.0 后,引入了重大特性——幂等性与事务。为什么讲事务还有 Q 一下幂等性呢?因为事务实际上就是基于幂等性实现的,因此,了解事务是如何工作之前我们还得了解幂等性是如何工作的。

本文力求以最简明的语言让读者明白事务的工作流程,但不会过多的深究原理。

本文的主要内容有:

  • 什么是幂等性?如何开启幂等性?✅
  • 幂等性的工作原理是什么?✅
  • 什么是 Kafka 事务?如何开启事务?✅
  • Kafka 事务的工作原理是什么?✅

二、幂等性

Producer 无论向 Broker 发送多少次重复的数据,Broker 端只会持久化一条,保证数据不会重复。 幂等性可以通过生产者配置项 enable.idempotence 关闭,为什么是关闭,因为它默认开启!

1. 幂等性的工作原理

幂等性的工作原理很简单,每条消息都有一个「主键」,这个主键由 <PID, Partition, SeqNumber> 组成,他们分别是:

  • PID:ProducerID,每个生产者启动时,Kafka 都会给它分配一个 ID,ProducerID 是生产者的唯一标识,需要注意的是,Kafka 重启也会重新分配 PID
  • Partition:消息需要发往的分区号
  • SeqNumber:生产者,他会记录自己所发送的消息,给他们分配一个自增的 ID,这个 ID 就是 SeqNumber,是该消息的唯一标识

对于主键相同的数据,Kafka 是不会重复持久化的,它只会接收一条,但由于是原理的限制,幂等性也只能保证单分区、单会话内的数据不重复,如果 Kafka 挂掉,重新给生产者分配了 PID,还是有可能产生重复的数据,这就需要另一个特性来保证了——Kafka 事务。

image-20220719151131299

三、Kafka 事务

Kafka 事务基于幂等性实现,通过事务机制,Kafka 可以实现对多个 Topic 、多个 Partition 的原子性的写入,即处于同一个事务内的所有消息,最终结果是要么全部写成功,要么全部写失败。

Kafka 事务分为生产者事务和消费者事务,但它们并不是强绑定的关系,消费者主要依赖自身对事务进行控制,因此这里我们主要讨论的是生产者事务。

1. 如何开启事务?

在详细介绍事务的原理之前,我们先来看一个简单的事务用例。

创建一个 Producer,指定一个事务 ID:

Properties properties = new Properties();
properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
properties.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//设置事务ID,必须
properties.setProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "transactional_id_1");
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

使用事务发送消息:

// 初始化事务
producer.initTransactions();
// 开启事务
producer.beginTransaction();
​
//发送10条消息往kafka,假如中间有异常,所有消息都会发送失败
try {
    for (int i = 0; i < 10; i++) {
        producer.send(new ProducerRecord<>("topic-test", "a message" + i));
    }
}
// 提交事务
producer.commitTransaction();
} catch (Exception e) {
    // 终止事务
    producer.abortTransaction();
} finally {
    producer.close();
}

2. 事务的工作原理

下图,事务的工作流程,自制:

image-20220720093844921

1)启动生产者,分配协调器

我们在使用事务的时候,必须给生产者指定一个事务 ID,生产者启动时,Kafka 会根据事务 ID 来分配一个事务协调器(Transaction Coordinator) 。每个 Broker 都有一个事务协调器,负责分配 PID(Producer ID) 和管理事务。

事务协调器的分配涉及到一个特殊的主题 __transaction_state,该主题默认有50个分区,每个分区负责一部分事务;Kafka 根据事务ID的hashcode值%50 计算出该事务属于哪个分区, 该分区 Leader 所在 Broker 的事务协调器就会被分配给该生产者。

分配完事务协调器后,该事务协调器会给生产者分配一个 PID,接下来生产者就可以准备发送消息了。

2)发送消息

生产者分配到 PID 后,要先告诉事务协调器要把详细发往哪些分区,协调器会做一个记录,然后生产者就可以开始发送消息了,这些消息与普通的消息不同,它们带着一个字段标识自己是事务消息。

当生产者事务内的消息发送完毕,会向事务协调器发送 Commit 或 Abort 请求,此时生产者的工作已经做完了,它只需要等待 Kafka 的响应。

3)确认事务

当生产者开始发送消息时,协调器判定事务开始。它会将开始的信息持久化到主题 __transaction_state 中。

当生产者发送完事务内的消息,或者遇到异常发送失败,协调器会收到 Commit 或 Abort 请求,接着事务协调器会跟所有主题通信,告诉它们事务是成功还是失败的。

如果是成功,主题会汇报自己已经收到消息,协调者收到所有主题的回应便确认了事务完成,并持久化这一结果。

如果是失败的,主题会把这个事务内的消息丢弃,并汇报给协调者,协调者收到所有结果后再持久化这一信息,事务结束;整个放弃事务的过程消费者是无感知的,它并不会收到这些数据。

四、写在最后

本文力求以最简明的语言让读者明白事务的工作流程,读起来「不累」,但实际上底层发生的事情任然有很多,我们这里不做过深的探究,感兴趣的同学可以根据不同的节点去进一步深入了解。

Kafka 专栏已经更新了三期啦:

  1. 「Kafka 专栏」- 001 Kafka 概述
  2. 「Kafka 专栏」- 002 Kafka 生产者详解
  3. 「Kafka专栏」- 003 生产者常用的调优手段

加餐:

  1. 本期:面试官:Kafka 事务是如何工作的?

本系列长期更新,内容根据资料整理和个人理解重新整理输出,原创保证。我是蛋糕,致力于以体系化的方式分享知识,点个关注不迷路!

\