前言
Kafka 事务(Transactions) 主要用于确保 生产者(Producer) 和 消费者(Consumer) 在数据传输过程中保持 原子性 和 一致性,避免出现脏读或不完整的数据消费。
一、Kafka 事务的作用
Kafka 的事务特性 保证了消息的 "Exactly-Once 语义(EOS)"。
-
避免部分提交数据(Partial Commit),如果 Producer 生产的消息部分写入,但发生故障,Kafka 事务可以确保这些消息要么全部可见,要么全部回滚。
- 跨多个分区的原子性(Atomicity Across Partitions),Kafka 事务允许一次性写入多个 Topic 分区,保证这些分区数据要么全部提交,要么全部撤销。
- 保证消费者端的 “Exactly-Once” 语义,Kafka Consumer 可以使用事务隔离级别只消费已提交的事务消息,防止读取未提交的部分消息。
二、Kafka 事务的实现
Kafka 事务依赖于 Producer 端的事务 ID(Transactional ID),Kafka 通过事务协调器(Transaction Coordinator)进行事务管理。
事务流程:
-
Producer 初始化事务(Init Transaction),Producer 需要设置 事务 ID,Kafka 会为其分配一个 事务协调器(Transaction Coordinator)。
-
Producer 开启事务(Begin Transaction),Producer 可以向 多个分区 发送消息。
-
Producer 发送消息(Send Messages),事务期间,Producer 持续向 Kafka 发送数据(数据暂存)。
-
Producer 提交事务(Commit Transaction),事务提交后,Kafka 保证所有消息对 Consumer 可见。
-
Producer 回滚事务(Abort Transaction),如果 Producer 发生异常,可以选择 回滚事务,Kafka 会丢弃这些未提交的数据。
三、代码实操
生产者(Producer)端事务
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "transaction-1"); // 设置事务 ID
props.put(ProducerConfig.ACKS_CONFIG, "all"); // 确保数据持久化
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
// 初始化事务
producer.initTransactions();
try {
producer.beginTransaction(); // 开始事务
producer.send(new ProducerRecord<>("topicA", "key1", "value1"));
producer.send(new ProducerRecord<>("topicB", "key2", "value2"));
producer.commitTransaction(); // 提交事务
} catch (Exception e) {
producer.abortTransaction(); // 发生异常,回滚事务
}
producer.close();
beginTransaction()
开启事务commitTransaction()
提交事务abortTransaction()
事务失败时回滚,防止数据不一致
消费者(Consumer)端事务
Kafka Consumer 开启事务隔离级别,只消费已提交的数据
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "group1");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG, "read_committed"); // 只读取已提交的事务数据
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("topicA"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.println("Consumed: " + record.value());
}
}
ISOLATION_LEVEL_CONFIG = "read_committed"
:确保 只消费已提交的事务数据,防止读取未提交的消息。
四、事务 vs 幂等性
特性 | kafka幂等性(Idempotence) | Kafka事务(Transactions) |
作用 | 避免Producer重复发送 | 确保跨分区、跨批次的事务一致性 |
机制 | 依赖Producer ID(PID)和序列号 | 依赖事务ID+事务协调器 |
数据范围 | 单分区 | 多分分区 |
幂等性保证 | Exactly-Once语义(单分区) | Exactly-Once语义(跨分区) |
五、Kafka 事务的限制
- 仅支持 Kafka 自身事务,不能和 外部数据库(MySQL, PostgreSQL)事务联动。
- 事务超时限制,Kafka 默认事务超时时间为 15 分钟,超时未提交会自动回滚。
- 性能损耗,事务需要额外的协调器操作,比普通 Producer 有一定的开销。
总结
-
Kafka 事务 适用于 跨多个分区的 Exactly-Once 语义,确保数据一致性。
-
Producer 端事务 通过 事务 ID 和 事务协调器 保障数据的完整性。
-
Consumer 端事务 需要
read_committed
级别确保只消费已提交的消息。 -
Kafka 幂等性 适用于 单个分区的数据去重,而事务适用于 多个分区的原子提交。