07. TransactionManager 事务管理器
本文档深入分析 Kafka Producer 的事务管理器实现,了解如何实现幂等性和事务语义。
目录
1. 事务管理概述
1.1 为什么需要事务
| 问题 | 说明 |
|---|---|
| 消息重复 | 网络重试可能导致消息重复写入 |
| 消息丢失 | 发送失败可能导致消息未写入 |
| 跨分区原子性 | 需要同时发送到多个分区的消息保持一致性 |
| 消费-生产原子性 | Consumer 消费消息后向其他 Topic 发送需要原子性 |
1.2 事务语义保证
┌─────────────────────────────────────────────────────────────┐
│ 事务语义保证 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 幂等性生产者 (enable.idempotence=true) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 消息 1 │────────▶│ Broker │────────▶│ 分区 0 │ │
│ │ Seq=0 │ │ │ │ │ │
│ └─────────┘ │ │ ├─────────┤ │
│ ┌─────────┐ │ │ │ 消息 1 │ │
│ │ 消息 2 │────────▶│ │ │ Seq=0 │ │
│ │ Seq=1 │ │ │ ├─────────┤ │
│ └─────────┘ │ │ │ 消息 2 │ │
│ │ │ │ Seq=1 │ │
│ 保证:即使重试,消息也不会重复写入 │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ 2. 事务生产者 (transactional.id 设置) │
│ │
│ beginTransaction() │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 事务边界 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Topic A │ │ Topic B │ │ Topic C │ │ │
│ │ │ Msg 1 │ │ Msg 2 │ │ Msg 3 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ commitTransaction() / abortTransaction() │
│ │
│ 保证:所有消息要么全部成功,要么全部失败 │
│ │
└─────────────────────────────────────────────────────────────┘1.3 TransactionManager 的职责
java
/**
* TransactionManager 核心职责:
* 1. 维护事务状态
* 2. 管理 Producer ID 和序列号
* 3. 处理事务相关的 RPC 请求
* 4. 协调消费-生产事务
*/
public class TransactionManager {
// 事务配置
private final String transactionalId;
private final int transactionTimeoutMs;
private final long retryBackoffMs;
// 事务状态
private volatile State state;
private volatile ProducerIdAndEpoch producerIdAndEpoch;
// 事务中的分区
private final Set<TopicPartition> partitionsInTransaction;
private final Set<TopicPartition> pendingPartitionsInTransaction;
private final Set<TopicPartition> partitionsAdded;
// 序列号管理
private final Map<TopicPartition, Integer> sequenceNumbers;
private final Map<TopicPartition, Integer> lastAckedSequence;
// 待发送的请求队列
private final PriorityQueue<TransactionBatch> transactionBatches;
}2. TransactionManager 结构
2.1 核心字段
java
public class TransactionManager {
// ============ 配置字段 ============
// 事务 ID,标识一个事务性生产者实例
// 用于恢复未完成的事务和实现 fencing
private final String transactionalId;
// 事务超时时间(默认 60 秒)
private final int transactionTimeoutMs;
// 重试退避时间
private final long retryBackoffMs;
// ============ 状态字段 ============
// 当前事务状态(线程安全)
private volatile State state;
// Producer ID 和 Epoch
// PID:唯一标识生产者实例
// Epoch:单调递增,用于 fencing 旧生产者
private volatile ProducerIdAndEpoch producerIdAndEpoch;
// ============ 事务跟踪字段 ============
// 当前事务中已发送消息的分区
private final Set<TopicPartition> partitionsInTransaction;
// 等待加入事务的分区
private final Set<TopicPartition> pendingPartitionsInTransaction;
// 新增的分区(用于发送 TxnOffsetCommit)
private final Set<TopicPartition> partitionsAdded;
// 消费者组协调器(用于消费-生产事务)
private volatile Node consumerGroupCoordinator;
// ============ 幂等性字段 ============
// 每个分区的下一个序列号
private final Map<TopicPartition, Integer> sequenceNumbers;
// 每个分区最后确认的序列号
private final Map<TopicPartition, Integer> lastAckedSequence;
// 每个分区正在发送的批次(用于处理乱序确认)
private final Map<TopicPartition, List<ProducerBatch>> inflightBatchesByPartition;
}2.2 状态管理
java
/**
* 事务状态枚举
*/
public enum State {
UNINITIALIZED, // 未初始化
INITIALIZING, // 正在初始化(获取 PID)
READY, // 准备就绪,可以开始事务
IN_TRANSACTION, // 事务进行中
COMMITTING_TRANSACTION, // 正在提交事务
ABORTING_TRANSACTION, // 正在中止事务
ABORTABLE_ERROR, // 可中止的错误状态
FATAL_ERROR, // 致命错误,生产者必须关闭
}2.3 请求队列
java
/**
* 事务请求队列管理
*/
private final PriorityQueue<TransactionBatch> pendingBatches;
/**
* 事务批次的排序依据
*/
class TransactionBatch implements Comparable<TransactionBatch> {
final long baseSequence;
final ProducerBatch batch;
@Override
public int compareTo(TransactionBatch other) {
// 按序列号排序,确保按顺序发送
return Long.compare(this.baseSequence, other.baseSequence);
}
}3. 事务状态机
3.1 事务状态定义
┌─────────────────────────────────────────────────────────────────────────┐
│ 事务状态转换图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ UNINITIALIZED│ │
│ └──────┬──────┘ │
│ │ initTransactions() │
│ ▼ │
│ ┌─────────────┐ 获取 PID 成功 ┌─────────┐ │
│ │ INITIALIZING │──────────────────────▶│ READY │ │
│ └─────────────┘ └────┬────┘ │
│ │ │
│ beginTransaction() │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ IN_TRANSACTION│ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────────────────────────────────┼──────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌────────────────┐ ┌────────────┐ │
│ │ ABORTING_ │ │ COMMITTING_ │ │ ABORTABLE_ │ │
│ │ TRANSACTION │ │ TRANSACTION │ │ ERROR │ │
│ └──────┬──────┘ └───────┬────────┘ └─────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ READY │ │ READY │ │ READY │ │
│ │ (中止成功) │ │ (提交成功) │ │ (中止成功) │ │
│ └─────────────┘ └──────────────┘ └────────────┘ │
│ │
│ 任何状态 ─────────────────────────────────────────────────────────▶ │
│ ┌─────────────┐ │
│ │ FATAL_ERROR│ │
│ └─────────────┘ │
│ (生产者必须关闭) │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.2 状态转换源码分析
java
/**
* 状态转换方法
*/
private void transitionTo(State targetState) {
State currentState = this.state;
// 验证状态转换是否合法
switch (targetState) {
case INITIALIZING:
if (currentState != State.UNINITIALIZED) {
throw new IllegalStateException(
"Cannot transition from " + currentState + " to INITIALIZING");
}
break;
case READY:
if (currentState != State.INITIALIZING
&& currentState != State.COMMITTING_TRANSACTION
&& currentState != State.ABORTING_TRANSACTION
&& currentState != State.ABORTABLE_ERROR) {
throw new IllegalStateException(
"Cannot transition from " + currentState + " to READY");
}
break;
case IN_TRANSACTION:
if (currentState != State.READY) {
throw new IllegalStateException(
"Cannot call beginTransaction before initTransactions");
}
break;
case COMMITTING_TRANSACTION:
if (currentState != State.IN_TRANSACTION) {
throw new IllegalStateException(
"Cannot commit if not in transaction");
}
break;
case ABORTING_TRANSACTION:
if (currentState != State.IN_TRANSACTION
&& currentState != State.ABORTABLE_ERROR) {
throw new IllegalStateException(
"Cannot abort if not in transaction or abortable error");
}
break;
case FATAL_ERROR:
// 任何状态都可以转到 FATAL_ERROR
break;
}
this.state = targetState;
}4. 事务操作流程
4.1 initTransactions() - 初始化
java
/**
* 初始化事务状态,获取 Producer ID
*/
public void initTransactions() {
ensureTransactional();
// 验证状态
if (currentState != State.UNINITIALIZED) {
throw new IllegalStateException(
"Cannot call initTransactions again after successful init");
}
// 转换为 INITIALIZING 状态
transitionTo(State.INITIALIZING);
// 构建 InitProducerId 请求
InitProducerIdRequest.Builder builder = new InitProducerIdRequest.Builder(
transactionalId,
transactionTimeoutMs
);
// 发送请求,获取 PID
enqueueRequest(builder, (response) -> {
InitProducerIdResponse initResponse = (InitProducerIdResponse) response;
if (initResponse.error() == Errors.NONE) {
// 成功获取 PID
producerIdAndEpoch = new ProducerIdAndEpoch(
initResponse.producerId(),
initResponse.epoch()
);
// 初始化序列号
sequenceNumbers.clear();
lastAckedSequence.clear();
// 转换到 READY 状态
transitionTo(State.READY);
} else {
// 处理错误
handleInitProducerIdError(initResponse.error());
}
});
}InitProducerId 流程:
Producer TransactionCoordinator
│ │
│ 1. InitProducerIdRequest │
│ (transactional_id, timeout) │
│ ─────────────────────────────────────────▶│
│ │
│ │ 2. 检查/创建事务状态
│ │ - 如果是新事务,分配 PID
│ │ - 如果是已有事务,递增 Epoch
│ │ (fencing 旧生产者)
│ │
│ 3. InitProducerIdResponse │
│ (producer_id, epoch) │
│ ◀─────────────────────────────────────────│4.2 beginTransaction() - 开始事务
java
/**
* 开始一个新事务
*/
public void beginTransaction() throws ProducerFencedException {
ensureTransactional();
// 验证状态
if (currentState != State.READY) {
throw new IllegalStateException(
"Cannot begin a transaction before initTransactions");
}
// 清理上一次事务的状态
partitionsInTransaction.clear();
pendingPartitionsInTransaction.clear();
partitionsAdded.clear();
// 转换到 IN_TRANSACTION 状态
transitionTo(State.IN_TRANSACTION);
}4.3 sendOffsetsToTransaction() - 发送 Offset
java
/**
* 将消费者位移发送到事务中
* 用于消费-生产原子性:消费消息后,将位移提交与消息发送作为同一事务
*/
public void sendOffsetsToTransaction(
Map<TopicPartition, OffsetAndMetadata> offsets,
ConsumerGroupMetadata groupMetadata) throws ProducerFencedException {
ensureTransactional();
// 验证状态
if (currentState != State.IN_TRANSACTION) {
throw new KafkaException(
"Cannot send offsets if not in transaction");
}
// 添加到待发送列表
pendingTxnOffsetCommits.put(groupMetadata, offsets);
// 设置消费者组协调器
this.consumerGroupCoordinator = groupMetadata.coordinator();
}
/**
* 实际发送 TxnOffsetCommit 请求
*/
void sendTxnOffsetCommitRequest() {
for (Map.Entry<ConsumerGroupMetadata, Map<TopicPartition, OffsetAndMetadata>> entry :
pendingTxnOffsetCommits.entrySet()) {
ConsumerGroupMetadata groupMetadata = entry.getKey();
Map<TopicPartition, OffsetAndMetadata> offsets = entry.getValue();
TxnOffsetCommitRequest.Builder builder = new TxnOffsetCommitRequest.Builder(
groupMetadata.groupId(),
producerIdAndEpoch.producerId,
producerIdAndEpoch.epoch,
offsets
);
// 发送请求
enqueueRequest(builder, (response) -> {
TxnOffsetCommitResponse commitResponse = (TxnOffsetCommitResponse) response;
handleTxnOffsetCommitResponse(commitResponse);
});
}
pendingTxnOffsetCommits.clear();
}4.4 commitTransaction() - 提交事务
java
/**
* 提交当前事务
*/
public void commitTransaction() throws ProducerFencedException {
ensureTransactional();
// 验证状态
if (currentState != State.IN_TRANSACTION) {
throw new IllegalStateException(
"Cannot commit if not in transaction");
}
// 转换到 COMMITTING_TRANSACTION 状态
transitionTo(State.COMMITTING_TRANSACTION);
// 1. 发送所有未发送的 TxnOffsetCommit 请求
sendTxnOffsetCommitRequest();
// 2. 等待所有 Produce 请求完成
waitForAllBatchesToComplete();
// 3. 发送 EndTxn 请求(提交)
EndTxnRequest.Builder builder = new EndTxnRequest.Builder(
transactionalId,
producerIdAndEpoch.producerId,
producerIdAndEpoch.epoch,
TransactionResult.COMMIT
);
enqueueRequest(builder, (response) -> {
EndTxnResponse endTxnResponse = (EndTxnResponse) response;
if (endTxnResponse.error() == Errors.NONE) {
// 事务提交成功
transitionTo(State.READY);
} else {
handleEndTxnError(endTxnResponse.error());
}
});
}事务提交流程:
Producer Broker(s) GroupCoordinator TransactionCoordinator
│ │ │ │
│ 1. Produce │ │ │
│ (with PID/Epoch)│ │ │
│ ────────────────▶│ │ │
│ │ │ │
│ 2. Produce │ │ │
│ (with PID/Epoch)│ │ │
│ ────────────────▶│ │ │
│ │ │ │
│ 3. TxnOffsetCommit │ │
│ (pending offsets) │ │
│ ──────────────────────────────────────▶ │
│ │ │ │
│ 4. commitTransaction() │
│ │ │ │
│ 5. EndTxn(COMMIT) │
│ ─────────────────────────────────────────────────────────────▶│
│ │ │ │
│ │ │ 6. 写入事务标记 │
│ │ │ (COMMIT) │
│ │ │ │
│ 7. 事务完成 │ │ │
│ ◀────────────────────────────────────────────────────────────│4.5 abortTransaction() - 中止事务
java
/**
* 中止当前事务
*/
public void abortTransaction() throws ProducerFencedException {
ensureTransactional();
// 验证状态
if (currentState != State.IN_TRANSACTION
&& currentState != State.ABORTABLE_ERROR) {
throw new IllegalStateException(
"Cannot abort if not in transaction or abortable error");
}
// 转换到 ABORTING_TRANSACTION 状态
transitionTo(State.ABORTING_TRANSACTION);
// 1. 清空所有未完成请求
abortAllPendingBatches();
// 2. 清空未发送的位移提交
pendingTxnOffsetCommits.clear();
// 3. 发送 EndTxn 请求(中止)
EndTxnRequest.Builder builder = new EndTxnRequest.Builder(
transactionalId,
producerIdAndEpoch.producerId,
producerIdAndEpoch.epoch,
TransactionResult.ABORT
);
enqueueRequest(builder, (response) -> {
EndTxnResponse endTxnResponse = (EndTxnResponse) response;
if (endTxnResponse.error() == Errors.NONE) {
// 事务中止成功
transitionTo(State.READY);
} else {
handleEndTxnError(endTxnResponse.error());
}
});
}5. 幂等性实现
5.1 PID (Producer ID) 分配
java
/**
* Producer ID 和 Epoch 信息
*/
public static class ProducerIdAndEpoch {
public static final ProducerIdAndEpoch NONE = new ProducerIdAndEpoch(-1, (short) -1);
public final long producerId; // 生产者唯一标识
public final short epoch; // 生产者世代号
public ProducerIdAndEpoch(long producerId, short epoch) {
this.producerId = producerId;
this.epoch = epoch;
}
}Epoch 的作用:
场景:生产者实例 A 崩溃,新实例 B 启动
1. 实例 A 持有 PID=100, Epoch=0
2. A 崩溃
3. 实例 B 启动,申请相同 transactional.id
4. Coordinator 返回 PID=100, Epoch=1
5. A 如果恢复并尝试发送消息:
- PID=100, Epoch=0
- Broker 发现 Epoch 不匹配
- 返回 PRODUCER_FENCED 错误
- A 必须关闭(被 fence 掉)5.2 Sequence Number 管理
java
/**
* 序列号管理
*/
public class TransactionManager {
// 每个分区的下一个序列号
private final Map<TopicPartition, Integer> sequenceNumbers;
/**
* 获取下一个序列号
*/
public synchronized int sequenceNumber(TopicPartition topicPartition) {
return sequenceNumbers.getOrDefault(topicPartition, 0);
}
/**
* 递增序列号
*/
public synchronized void incrementSequenceNumber(TopicPartition topicPartition, int increment) {
int current = sequenceNumbers.getOrDefault(topicPartition, 0);
sequenceNumbers.put(topicPartition, current + increment);
}
}
/**
* ProducerBatch 中包含序列号信息
*/
public final class ProducerBatch {
// 该批次的起始序列号
private final int baseSequence;
// 该批次的 Producer ID
private final long producerId;
// 该批次的 Epoch
private final short producerEpoch;
}5.3 去重机制
Broker 端去重:
每个分区维护:
┌──────────────────────────────────────────────────────┐
│ Partition Log │
├──────────────────────────────────────────────────────┤
│ PID: 100 │
│ Current Epoch: 1 │
│ Last Sequence: 5 ← 最后确认的序列号 │
│ │
│ 已确认的消息: │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ Seq: 0 │ Seq: 1 │ Seq: 2 │ Seq: 3 │ ... │
│ │ Offset:0│ Offset:1│ Offset:2│ Offset:5│ │
│ └─────────┴─────────┴─────────┴─────────┘ │
└──────────────────────────────────────────────────────┘
新消息到达时的检查:
1. PID 是否存在?
- 不存在:新生产者,接受消息
- 存在:检查 Epoch
2. Epoch 是否匹配?
- 小于当前 Epoch:生产者已被 fence,拒绝
- 等于当前 Epoch:继续检查序列号
- 大于当前 Epoch:异常情况
3. Sequence Number 检查:
- Seq < Last Sequence: 重复消息,丢弃但返回成功
- Seq = Last Sequence + 1: 正常顺序,接受
- Seq > Last Sequence + 1: 消息丢失,返回 OUT_OF_ORDER_SEQUENCEjava
/**
* Broker 端去重逻辑(简化版)
*/
class ProducerStateEntry {
long producerId;
short epoch;
int lastSequence;
long lastOffset;
boolean isDuplicate(int sequence) {
return sequence <= lastSequence;
}
boolean isValidSequence(int sequence) {
return sequence == lastSequence + 1;
}
}
void validateAndAppend(ProducerStateEntry entry, int sequence, Record record) {
if (entry.epoch < expectedEpoch) {
throw new ProducerFencedException("Producer fenced by new epoch");
}
if (entry.isDuplicate(sequence)) {
// 重复消息,忽略但返回成功
return;
}
if (!entry.isValidSequence(sequence)) {
throw new OutOfOrderSequenceException("Invalid sequence number");
}
// 正常消息,追加到日志
append(record);
entry.lastSequence = sequence;
}6. 事务超时与恢复
6.1 transaction.timeout.ms
java
/**
* 事务超时配置(默认 60000ms = 1分钟)
*/
props.put("transaction.timeout.ms", 60000);
/**
* 超时检查在 TransactionCoordinator 中进行:
*/
class TransactionCoordinator {
void maybeExpireTransactions() {
for (TransactionMetadata txn : activeTransactions) {
if (txn.isExpired(transactionTimeoutMs)) {
// 超时事务强制中止
abortTransaction(txn);
}
}
}
}6.2 超时处理流程
事务超时处理:
1. 生产者开始事务
beginTransaction()
2. 发送若干消息
producer.send(...) × N
3. 生产者崩溃或网络中断
(事务处于 IN_TRANSACTION 状态)
4. 事务超时(默认 60 秒)
TransactionCoordinator 检测到超时
5. 协调器强制中止事务
- 写入 Abort 标记到 __transaction_state
- 通知相关 Broker 丢弃未确认消息
6. 消费者可见性
- 事务中的消息对消费者不可见
- 直到事务提交后才可见(read_committed 隔离级别)6.3 事务恢复机制
java
/**
* 生产者重启后的事务恢复
*/
public void initTransactions() {
// 发送 InitProducerId 请求
// Coordinator 可能返回之前的 PID 并递增 Epoch
// 这会 fence 掉崩溃的旧生产者实例
// 可能的情况:
// 1. 无未完成事务:正常初始化
// 2. 有未完成事务:
// - 已提交:保持提交状态
// - 未提交:等待超时后中止,或新生产者决定提交/中止
}事务恢复流程:
Producer 崩溃后重启:
1. 新实例启动,调用 initTransactions()
2. 申请相同 transactional.id
3. Coordinator 响应:
┌─────────────────────────────────────────────────────────────┐
│ 情况 A: 无未完成事务 │
│ - 返回新 PID │
│ - 正常开始新事务 │
├─────────────────────────────────────────────────────────────┤
│ 情况 B: 有待提交事务 │
│ - 返回相同 PID,Epoch+1 │
│ - fence 旧生产者 │
│ - 允许新生产者决定提交或中止 │
├─────────────────────────────────────────────────────────────┤
│ 情况 C: 事务已超时 │
│ - Coordinator 已自动中止 │
│ - 返回相同 PID,Epoch+1 │
│ - 新生产者可以开始新事务 │
└─────────────────────────────────────────────────────────────┘
4. 新生产者可以继续发送消息上一章: 06. Producer 拦截器下一章: 08. Producer 配置详解