目录
[1. 事务核心组件](#1. 事务核心组件)
[1.1 幂等性生产者(Idempotent Producer)](#1.1 幂等性生产者(Idempotent Producer))
[1.2 事务协调器(TransactionCoordinator)](#1.2 事务协调器(TransactionCoordinator))
[1.3 事务日志(Transaction Log)](#1.3 事务日志(Transaction Log))
[2. 事务执行流程](#2. 事务执行流程)
[2.1 事务初始化](#2.1 事务初始化)
[2.2 发送消息](#2.2 发送消息)
[2.3 事务提交(两阶段提交)](#2.3 事务提交(两阶段提交))
[2.4 事务完成](#2.4 事务完成)
[3. 消费者事务隔离](#3. 消费者事务隔离)
[3.1 隔离级别](#3.1 隔离级别)
[3.2 实现机制](#3.2 实现机制)
[4. 异常处理与容错](#4. 异常处理与容错)
[4.1 生产者宕机](#4.1 生产者宕机)
[4.2 协调器宕机](#4.2 协调器宕机)
[4.3 Broker宕机](#4.3 Broker宕机)
[5. 关键源码解析](#5. 关键源码解析)
[5.1 事务协调器核心逻辑](#5.1 事务协调器核心逻辑)
[5.2 两阶段提交实现](#5.2 两阶段提交实现)
[5.3 消费者过滤未提交消息](#5.3 消费者过滤未提交消息)
[6. 事务配置与使用](#6. 事务配置与使用)
[6.1 生产者配置](#6.1 生产者配置)
[6.2 消费者配置](#6.2 消费者配置)
[7. 事务性能与限制](#7. 事务性能与限制)
- 幂等生产者 :通过
ProducerID
和SequenceNumber
去重,避免消息重复(源码见ProducerStateManager
)。- 事务协调器(TransactionCoordinator):
- 每个事务绑定一个Coordinator,处理
BEGIN_TRANSACTION
、COMMIT
/ABORT
请求。- 事务状态存储在内部Topic
__transaction_state
中(通过TransactionStateManager
管理)。
- 两阶段提交:
- 阶段1:标记事务为"预提交",写入所有参与分区的数据。
- 阶段2:写入
COMMIT
标记到事务日志,消费者仅可见已提交的事务消息。
Kafka事务机制通过幂等性生产者 、事务协调器(TransactionCoordinator) 和 两阶段提交(2PC) 实现跨分区的原子性写入,确保消息要么全部提交,要么全部丢弃。以下是核心实现机制:
1. 事务核心组件
1.1 幂等性生产者(Idempotent Producer)
- 作用:确保单分区内消息不重复。
- 实现机制:
-
- PID(Producer ID):每个生产者实例唯一,由Broker分配。
- Sequence Number:每个消息的单调递增序列号,Broker校验序列号连续性。
- 源码类 :
ProducerStateManager
(管理PID与序列号)。
1.2 事务协调器(TransactionCoordinator)
- 作用:管理事务生命周期,协调事务提交或中止。
- 实现机制:
-
- 每个事务绑定一个协调器(通过事务ID哈希选择Broker)。
- 维护事务状态机(
TransactionState
),存储在内部Topic__transaction_state
。 - 源码类 :
TransactionCoordinator
、TransactionStateManager
。
1.3 事务日志(Transaction Log)
- 作用:持久化事务状态,防止协调器宕机后数据丢失。
- 存储位置 :内部Topic
__transaction_state
,每个分区对应一个协调器。 - 数据格式 :事务ID、PID、状态(
PrepareCommit
、Completed
等)、超时时间。
2. 事务执行流程
2.1 事务初始化
- 生产者初始化事务:
-
- 调用
initTransactions()
,向协调器注册事务ID,获取PID。 - 协调器在
__transaction_state
中记录事务元数据。
- 调用
2.2 发送消息
- 发送事务消息:
-
- 生产者发送消息时携带PID、序列号、事务ID。
- Broker将消息写入日志,但标记为未提交(对消费者不可见)。
2.3 事务提交(两阶段提交)
- 阶段1:Prepare Commit
生产者向协调器发送EndTxnRequest
,协调器将事务状态置为PrepareCommit
,并持久化到事务日志。 - 阶段2:Commit Markers写入
协调器向所有涉及的分区Leader发送WriteTxnMarkers
请求,Leader在日志中写入事务提交标记(Control Batch)。
2.4 事务完成
- Broker将事务消息标记为已提交 ,消费者可读取(需配置
isolation.level=read_committed
)。
3. 消费者事务隔离
3.1 隔离级别
- read_committed:仅消费已提交的事务消息(跳过未提交的Control Batch)。
- read_uncommitted:消费所有消息(默认模式,不保证事务原子性)。
3.2 实现机制
- 消费者在拉取消息时,Broker根据隔离级别过滤未提交的事务消息。
- 源码逻辑 :
KafkaConsumer
的fetcher
模块解析Control Batch,决定是否跳过消息。
4. 异常处理与容错
4.1 生产者宕机
- 事务超时(
transaction.timeout.ms
):协调器自动中止未完成的事务。 - 新生产者实例需重新初始化事务,旧事务状态由协调器清理。
4.2 协调器宕机
- 事务日志持久化在
__transaction_state
,新协调器加载日志恢复状态。
4.3 Broker宕机
- 副本机制保证事务日志和消息日志的高可用,Leader切换后继续处理事务。
5. 关键源码解析
5.1 事务协调器核心逻辑
//事务状态管理(TransactionStateManager)
public class TransactionStateManager {
// 持久化事务状态到__transaction_state
def appendTransactionToLog(transactionState: TransactionState): Unit = {
val records = List(new SimpleRecord(transactionState.toBytes))
transactionLog.append(records)
}
}
5.2 两阶段提交实现
// 协调器发送提交标记(TransactionCoordinator)
private def sendTxnMarkers(transactionState: TransactionState): Unit = {
// 向所有分区Leader发送WriteTxnMarkersRequest
transactionState.partitions.foreach { partition =>
val request = new WriteTxnMarkersRequest.Builder(partition, Commit)
sendRequestToLeader(request)
}
}
5.3 消费者过滤未提交消息
// 消费者拉取消息过滤(ConsumerFetcherThread)
private def filterCommittedMessages(records: ConsumerRecords): ConsumerRecords = {
if (isolationLevel == IsolationLevel.READ_COMMITTED) {
records.filter(_.controlBatchType != ControlBatchType.ABORT)
} else {
records
}
}
6. 事务配置与使用
6.1 生产者配置
props.put("enable.idempotence", "true"); // 开启幂等性
props.put("transactional.id", "my-tx-id"); // 必须设置事务ID
props.put("transaction.timeout.ms", "60000"); // 事务超时时间
6.2 消费者配置
props.put("isolation.level", "read_committed"); // 仅消费已提交消息
7. 事务性能与限制
- 性能开销:事务引入两阶段提交和日志持久化,吞吐量下降约20%-30%。
- 限制:
-
- 事务仅支持单会话(一个生产者实例)。
- 事务消息的消费者必须使用Kafka Consumer API(不支持旧版基于ZooKeeper的消费者)。
总结
Kafka事务通过以下机制实现跨分区的原子性:
- 幂等性生产者:避免单分区消息重复。
- 事务协调器与两阶段提交:确保所有分区要么全部提交,要么全部回滚。
- 事务日志持久化:保障协调器故障恢复后状态一致。
- 消费者隔离级别:控制事务消息的可见性。
正确配置后,Kafka事务可支持金融级场景的精确一次(Exactly-Once)语义