Loading

Kafka消息语义&幂等性&事务

  1. 最多一次:消息最多被消费一次,可能丢失
  2. 最少一次:消息最少被消费一次,可能被重复消费多次
  3. 精确一次:消息会且只会被消费一次

Producer默认语义

默认情况下,Kafka会在producer的消息写入到分区leader副本的磁盘日志文件中后发送响应,若producer没接收到响应,它会尝试重新发送,所以是最少一次语义。

Consumer默认语义

对于Consumer来说,提供什么语义就看什么时候提交位移。

  1. 获取消息->提交位移->处理:最多一次,因为提交位移后可能崩溃
  2. 获取消息->处理->提交位移:最少一次

幂等性Producer

用于支持精确一次语义(EOS)的一个手段。

发送给broker的每一批消息都被分配一个序列号,broker会将它保存到日志文件中,此外,每一个Producer会被分配一个PID(PID, 分区号)的二元组唯一确定一个最新消息序列号,broker接到消息时,根据(pid, 分区号)找到该Producer在该分区下的最新消息序列号S,若新消息的序列号小于等于S,拒接这个消息。

缺陷:无法实现多个producer共同提供EOS语义,无法提供producer多次会话间的EOS语义(重启后原PID失效),只对单分区的消息发布提供EOS语义,对consumer无法提供任何保证

Producer幂等性使用enable.idempotence开启

事务

  1. 事务真正提供了端到端的精确一次语义,端到端指Producer到Consumer
  2. 事务提供了对多个topic及多个partitions的原子性写入,事务中的消息要么全部写入成功,要么全部写入失败

Kafka事务的实现依赖了幂等生产者

事务典型使用场景

在公司看了半天,还是有点懵,所以回家先看下典型用例,学下API的使用,然后再学原理。

消费-处理-生产,即从一个topic中消费消息,经过一些业务处理,再发到另一个topic。

如果没有事务保证,处理过程中发生问题,可能导致

  1. 消息丢失,消费者提交了offset但没提交到新topic,或压根没处理到
  2. 消息重复消费,已经提交到新topic了但消费者没提交offset

事务保证处理并提交到新topic与消费者提交offset要么同时成功,要么同时失败

事务API使用

  1. 要想开启事务,首先要在Producer中提供唯一的transactional.id,并设置enable.idempotencetrue

    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。

  2. 调用producer.initTransaction()初始化事务,producer的生命周期中只初始化一次

  3. Consumer拉取消息

  4. 准备处理消息了,调用producer.beginTransaction()先开启事务

  5. Consumer处理消息

  6. 将处理后的消息调用producer.send()发送到新topic

  7. 调用producer.sendOffsetToTransaction()发布consumer读取的offset,可以通过拉到的最后一条消息的offset+1获得

  8. 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();  // 发生异常 中断事务
  }
}

未完,先睡...

posted @ 2022-10-31 18:00  yudoge  阅读(94)  评论(0编辑  收藏  举报