Kafka消息语义&幂等性&事务
- 最多一次:消息最多被消费一次,可能丢失
- 最少一次:消息最少被消费一次,可能被重复消费多次
- 精确一次:消息会且只会被消费一次
Producer默认语义
默认情况下,Kafka会在producer的消息写入到分区leader副本的磁盘日志文件中后发送响应,若producer没接收到响应,它会尝试重新发送,所以是最少一次语义。
Consumer默认语义
对于Consumer来说,提供什么语义就看什么时候提交位移。
- 获取消息->提交位移->处理:最多一次,因为提交位移后可能崩溃
- 获取消息->处理->提交位移:最少一次
幂等性Producer
用于支持精确一次语义(EOS)的一个手段。
发送给broker的每一批消息都被分配一个序列号,broker会将它保存到日志文件中,此外,每一个Producer会被分配一个PID,(PID, 分区号)的二元组唯一确定一个最新消息序列号,broker接到消息时,根据(pid, 分区号)
找到该Producer在该分区下的最新消息序列号S,若新消息的序列号小于等于S,拒接这个消息。
缺陷:无法实现多个producer共同提供EOS语义,无法提供producer多次会话间的EOS语义(重启后原PID失效),只对单分区的消息发布提供EOS语义,对consumer无法提供任何保证
Producer幂等性使用
enable.idempotence
开启
事务
- 事务真正提供了端到端的精确一次语义,端到端指Producer到Consumer
- 事务提供了对多个topic及多个partitions的原子性写入,事务中的消息要么全部写入成功,要么全部写入失败
Kafka事务的实现依赖了幂等生产者
事务典型使用场景
在公司看了半天,还是有点懵,所以回家先看下典型用例,学下API的使用,然后再学原理。
消费-处理-生产,即从一个topic中消费消息,经过一些业务处理,再发到另一个topic。
如果没有事务保证,处理过程中发生问题,可能导致
- 消息丢失,消费者提交了offset但没提交到新topic,或压根没处理到
- 消息重复消费,已经提交到新topic了但消费者没提交offset
事务保证处理并提交到新topic与消费者提交offset要么同时成功,要么同时失败
事务API使用
-
要想开启事务,首先要在Producer中提供唯一的
transactional.id
,并设置enable.idempotence
为true
Properties prop = new Properties(); prop.put("transactional.id", "PRODUCER001_TRANSACTIONAL_10394"); prop.put("enable.idempotence", true);
考虑上面的幂等性Producer,PID对用户透明,是自动分配的,重启后会失效,所以它无法提供producer多次会话间的EOS语义,而
transactional.id
是用户自己指定的,它可以写死,一个Producer就用这一个ID,即使重启也用这个,这样broker就能在多次会话间唯一识别一个Producer。 -
调用
producer.initTransaction()
初始化事务,producer的生命周期中只初始化一次 -
Consumer拉取消息
-
准备处理消息了,调用
producer.beginTransaction()
先开启事务 -
Consumer处理消息
-
将处理后的消息调用
producer.send()
发送到新topic -
调用
producer.sendOffsetToTransaction()
发布consumer读取的offset,可以通过拉到的最后一条消息的offset+1获得 -
producer.commitTransaction()
提交整个事务
整个过程的代码示例:
producer.initTransactions();
while (true) {
ConsumerRecords records = consumer.poll(Long.MAX_VALUE);
producer.beginTransaction(); // 进入事务
try {
for (ConsumerRecord record : records)
producer.send(new ProducerRecord(“outputTopic”, record));
producer.sendOffsetsToTransaction(currentOffsets(consumer), my-consumerGroup-id);
producer.commitTransaction(); // 提交事务
} catch (Exception e) {
p.abortTransaction(); // 发生异常 中断事务
}
}
未完,先睡...