Kafka 事务以及幂等

Kafka提供了生产者发送消息的幂等性和事务特性来阻止消息的重复,这两种方式均适用于不同的应用场景,其中:

  • 消息的幂等性
    适用于消息在写入到服务器日志后,由于网络故障,生产者没有及时收到服务端的ACK消息,生产者误以为消息没有持久化到服务端,导致生产者重复发送该消息,造成了消息的重复现象,而幂等性就是为了解决该问题。
  • 生产者事务
    生产者事务有两种典型的用途,一种是将多个消息的提交操作作为一个原子操作,要么全部提交成功,要么全部提交失败,还有一种场景就是用于防止consume -> processor -> produce场景下的消息重复问题,即主题A的消息在被消费后,在processor进行处理后,再通过生产者将处理后的消息发送到主题B。仅仅依靠幂等性只能够保证主题A的生产者不重复发送消息,而无法保证消息在processor处理后并且发送到主题B后,如果当前processor宕机,并且在此之前没有及时提交主题A的消费位移,在processor再次启动后,主题A的消息被processor重复消费,并再次进行处理,从而导致主题B有重复的消息。

    上图中,由于消费位移没有及时提交给主题A,导致processor重启后,会再次拉取消息X,并重复上述流程。Kafka的事务就是为了解决这种场景导致的主题B中消息Y重复的问题。

本文将详细介绍这两种方式。


1 - 消息幂等

前面提到过,消息幂等是为了防止生产者没有及时收到ACK情况下重复发送消息导致队列中消息的重复。注意这里的ACK并非是TCP协议的ACK,而是Kafka在完成消息的持久化后,向生产者发送的应用层的ACK,表示已经收到消息并完成了同步,TCP层的ACK只能满足消息已经正确送达,不能保证Kafka已经将消息持久化到日志,下文中的ACK均表示应用层的ACK

为了解决上述问题,Kafka提供了幂等机制,简单说就是对接口的多次调用所产生的结果和调用一次是一致的。
如果需要使用幂等,在构造KafkaProducer的时候,需要修改以下参数:

  • enable.idempotence(即ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG)为true
  • 保证retries参数大于0(默认设置为Integer.MAX_VALUE),retries参数是消息在发送出去后没有及时收到ACK时的尝试次数。
  • max.in.flight.requests.per.connection参数不能大于5(默认设置为5)
  • acks参数为-1,即只有消息同步到所有分区时才会返回ACK

在打开enable.idempotence参数后,其余三个参数都会自动调整至默认值。

实现机制

为了实现生产者的幂等性,Kafka引入了Producer IDSequence Number两个概念

每个KafkaProducer在构造时都会被分配出一个Producer ID,每个分区都有一个序列号,从0开始递增,生产者发送一条消息到分区后,就会将[Producer ID, Partition ID]Partition ID为分区号)对应的序列号的值加上1,这个序列号由服务端维护。

Broker端为每一对[Producer ID, Partition ID]维护了一个序列号,当Broker端收到一个生产者发送的消息时,只有当该消息的序列号值比当前的值大1,才会接收它。如果差值小于1则说明消息被重复发送了,则会丢弃这条消息。如果差值大于1,则说明中间有消息尚未被写入,此时生产者会抛出OutOfOrderSequenceException异常。


2- 事务

Kafka事务可以保障一组消息要么全部发送成功,要么全部失败,并且可以跨多个分区工作。
前面提到过,事务的典型使用场景就是consume -> processor -> produce,在这种模式下消费和生产并存,消费者在提交消费位移的过程中可能出现问题导致重复消费消息。在这种场景下,使用事务可以将以下三步操作合并为一个原子操作:

  1. 从主题A的某个分区消费消息X
  2. 处理主题A的消息X后,得到消息Y,并将消息Y提交到主题B的某个分区
  3. 提交主题A该分区的消费位移

上述三个操作要么全部成功,要么全部失败。

使用方法

事务的控制需要通过KafkaProducer对象,在构造KafkaProducer前,需要设置以下参数:

  • transactional.idProducerConfig.TRANSACTIONAL_ID_CONFIG),即事务ID,保持不变即可。
  • enable.idempotenceProducerConfig.ENABLE_IDEMPOTENCE_CONFIG),事务幂等性,在设置transactional.id后该配置默认会设置为true
  • 其它参数和打开幂等时类似。

如果同一时间有两个相同事务ID的KafkaProducer被构造,那么前一个构造的KafkaProducer会抛出ProducerFencedException异常。

KafkaProducer提供了5个与事务相关的方法:

  • void initTransactions():初始化事务,需要在构造时配置好事务ID,否则会抛出IllegalStateException
  • void beginTransaction():开启一个事务
  • void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets, String consumerGroupId):向ID为consumerGroupId的消费者组提交消费位移,包含多个分区的不同位移信息
  • void commitTransaction():提交事务
  • void abortTransaction():回滚事务

consume -> processor -> produce场景下的示例代码:

KafkaConsumer<String, String> consumer = ...;  //构造消费者
consumer.subscribe(Collections.singletion("topic-A"));  //订阅主题topic-A

KafkaProducer<String, String> producer = ...;  //构造生产者
producer.initTransactions();  //初始化事务

while (!Thread.currentThread().isInterrupted()) {
	ConsumerRecords<String> records = consumer.poll(1L);
	Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();  //待提交的消费位移
	
	try {
		for (TopicPartition partition: records.partitions()) {
			long offset = -1L;
			for (ConsumerRecord<String, String> record : records.records(partition)) {
				//提交处理后的消息
				ProducerRecord<String, String> newRecord = new ProducerRecord("topic-B", /*省略内容*/);
				producer.send(newRecord);
				offset = record.offset();  //取最新的消息位移
			}
			
			if (offset != -1) {
				offsets.put(partition, new OffsetAndMetadata(offset + 1));
			}
		}
		//提交消费位移
		producer.sendOffsetsToTransaction(offsets, "consumer-group-id");
		producer.beginTransaction(); //提交事务
	} catch (Exception e) {
		producer.abortTransaction(); //遇到异常回滚事务
	}
}

注意初始化KafkaConsumer的时候需要将enable.auto.commit设置为false,即禁止自动提交消费位移。

隔离级别

在初始化KafkaConsumer的时候,还有一个值得注意的参数就是isolation.levelConsumerConfig.ISOLATION_LEVEL_CONFIG),即事务隔离级别,默认有两种:

  • read_committed:消费者不可以消费到尚未提交的事务内的消息
  • read_uncommitted:消费者可以消费到尚未提交的事务内的消息

事务隔离级别的实现主要是通过一种特殊的消息实现,这个消息被称为ControlBatch
例如,在开启事务后,依次提交消息message 1message 2message 3,然后提交事务:
在这里插入图片描述
KafkaConsumer的事务隔离级别为read_committed时,只有当收到ControlBatch时,才会将三条消息返回给应用层。如果事务隔离级别为read_uncommitted,那么即使没有收到ControlBatch,也会将前面的消息传递给业务层。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Kafka提供了两个关键的特性来确保消息的一致性和可靠性:幂等性和事务性。 1. 幂等性(Idempotent):Kafka的生产者可以配置为幂等生产者,即保证在发送消息时不会产生重复消息。幂等性意味着无论发送多少次相同的消息,最终结果都是一样的,不会导致副作用。幂等性生产者通过在消息中添加序列号来实现,Kafka在接收到重复消息时会自动去重,确保只有一条消息被写入。 2. 事务性(Transactional):Kafka从0.11版本开始引入了事务性支持。事务性消费者可以以事务的方式读取和处理消息,同时也支持事务性生产者在写入消息时保持原子性。事务性消费者可以确保读取的消息在被处理后不会被重复消费,并且在处理失败时可以回滚事务事务性生产者可以将多个写操作组合为一个原子事务,要么全部成功提交,要么全部回滚。 使用幂等性和事务性可以帮助确保在Kafka中进行消息的可靠处理和传递。幂等性消费者和事务性消费者可以避免重复消费和数据不一致的问题,而幂等性生产者和事务性生产者可以确保消息的原子性写入和可靠提交。 需要注意的是,启用事务性和幂等性特性会增加一定的性能开销,因此在使用时需要权衡性能和一致性的需求,并根据实际情况进行配置和调整。同时,事务性和幂等性特性也需要结合Kafka的相应API和配置进行正确的使用和管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值