Kafka010——事务消息

244 阅读5分钟

写在前面

消息传输级别

消息队列的主要功能就是将消息从生产者传输到消费者,而衡量一个消息队列传输消息保障一般是有三个级别:

  1. at most once:至多一次。消息可能会丢失,但绝不会重复传输
  2. at least once:至少一次。消息绝对不会丢失,但可能会重复传输
  3. exactly once:恰好一次。每条消息都肯定被且仅被成功传输一次,不会丢失,也不会有多余的重复。

而Kafka提供的消息传输保障级别是:at least once。他的消息传输保障机制主要有以下:

  1. Broker端针对消息存储有Replica机制,所以消息一旦成功写入Broker,便不会丢失
  2. Kafka针对Producer提供了消息写入重试机制,但如果因为网络问题导致Producer收到Kafka是否成功写入消息的回调,就会导致Producer重复写入
  3. Kafka针对Consumer的消息消费使用Offset机制,当Consumer批量拉取消息却没有及时确认Offset,这时发生Rebalance之类,就会触发重新的消费。

Kafka除了基本的at least once,也可以通过消息幂等和事务来实现 exactly once。

消息生产幂等

Kafka可以通过设置producer的客户端参数transactional.id为true来开启消息生产幂等。

Kafka通过引入producer id 和 序列号(sequence number)来实现消息生成的幂等性。这两个概念在接受Kafka消息日志格式V2版本时(这篇文章)有介绍过。这两个概念具体使用如下:

  1. producer在初始化时,会被分配一个全局唯一的 producer id,这个过程生产者无感。
  2. producer生产的,发往partition的每条消息,也都会分配一个序列号。序列号从0递增,且按<pid, partition>的维度区分。
  3. broker在内存中会为每个<pid, partition>维护一个序列号(seq_old),此时,当broker收到一条序列号为seq_new的信息
    1. 如果seq_new=seq_old+1,broker才会接收该消息。
    2. 如果seq_new<seq_old+1,说明这条消息是重复消息
    3. 如果seq_new>seq_old+1,说明消息有乱序,中间区间的消息可能丢失,kafka会返回一个异常。

需要注意,broker存储的序列号,是按照<pid, partition>来划分的,所以Kafka的消息幂等只能保证单producer单分区的幂等。

事务与事务协调器

事务的基本概念

kafka的事务可以跨分区,保证对多个分区的写入操作保持原子性,即多个分区的写入要么全部成功,要么全部失败。注意这里多个分区可以涉及不同的topic的不同分区。

  1. Kafka的事务可以保证跨生产者会话的消息幂等发送。即kafka保证不会同时有两个相同的transactionalID标识的producer在生产消息
  2. Kafka的事务也保证跨生产者会话的事务恢复。即当同一个transactionalID的生产者实例意外宕机,那么新的同transactionalID的事务仍然可以继续处理未提交的事务消息。
  3. Kafka不保证同一个事务的消息都能被消费者消费。这个原因有很多:可能事务消息被压缩处理、消费者可能跳跃消费消息、或者消费者没有被分配某个事务涉及的所有分区,导致无法获取事务所有的消息。

transactionalID

要使用Kafka的事务消息,生产程序需要提供唯一的transactionalID(可以通过客户端的transactional.id设置)。同时要使用事务消息,也需要保证消息幂等配置transactional.id开启。

producer epoch

在开启事物之后,producer在初始化时会通过transactionalID获取producerID,还会获取一个单调递增的producer epoch。

事务的基本使用方法

Kafka提供了事务的5个方法,如下:

  1. initTransactions():初始化一个事务(要求producer配置了transactional.id
  2. beginTransactions():开启事务
  3. sendOffsetsToTransactions():消费者可调用,用于提交消费事务消息内的offset
  4. commitTransactions():提交事务
  5. abortTransactions():终止事务

通常生产端使用事务发送消息的主要逻辑如下:

initTransactions()
beginTransactions()

try {
    // get some message for sending
    send(message)
    // ...
    commitTransactions()
} catch (SomeException e) {
    abortTransactions()
}

事务的基本实现原理与基本流程

Kafka事务是基于两阶段提交(Two-Phase Commit)协议。

  1. 开启事务:生产者在开始事务之前调用beginTransaction()方法,该方法会为该生产者分配一个唯一的事务ID。
  2. 发送消息:生产者在事务中使用send()方法发送消息到Kafka主题。这些消息在发送期间只会被缓存在生产者端,而不会立即提交到服务器。
  3. 预提交:当生产者完成消息发送后,调用commitTransaction()方法进行预提交。在预提交阶段,生产者将事务中的所有消息写入Kafka主题的特殊事务性分区中。
  4. 事务协调:Kafka通过事务协调器(Transaction Coordinator)来管理事务的提交和回滚。协调器负责与生产者和消费者进行通信,并确保事务的一致性。
  5. 决策阶段:在预提交后,事务协调器会协调所有参与该事务的生产者和消费者,并请求它们对事务进行决策。生产者和消费者将根据收到的请求进行回答,表示它们是否同意或拒绝该事务的提交。
  6. 提交或回滚:根据决策阶段的回答,事务协调器将决定是提交事务还是回滚事务。
    • 如果所有参与者都同意提交事务,事务协调器会向Kafka服务器发送commit请求,将事务中的消息提交到相应的主题分区中。此时事务被视为已提交。
    • 如果任何一个参与者拒绝提交事务,事务协调器会向Kafka服务器发送abort请求,将事务中的消息从特殊事务性分区中移除。此时事务被视为已回滚。
  7. 结束事务:无论事务是提交还是回滚,生产者在完成后都应调用endTransaction()方法来结束事务。这会清除生产者的事务状态并释放相关资源。