Kafka位移提交以及CommitFailedException

一、Kafka的消费位移

  Consumer 端有个位移的概念,它和消息在分区中的位移不是一回事儿,虽然它们的英文都是 Offset。它记录了Consumer 要消费的下一条消息的位移。这可能和你以前了解的有些出入,不过切记是下一条消息的位移,而不是目前最新消费消息的位移。Consumer 需要向 Kafka 汇报自己的位移数据,这个汇报过程被称为提交位移。从用户的角度来说,位移提交分为自动提交和手动提交;从 Consumer 端的角度来说,位移提交分为同步提交和异步提交。
  开启自动提交位移的方法很简单。Consumer 端有个参数 enable.auto.commit,把它设置为 true 或者压根不设置它就可以了。因为它的默认值就是 true,即 Java Consumer 默认就是自动提交位移的。如果启用了自动提交,Consumer 端还有个参数就派上用场了:auto.commit.interval.ms。它的默认值是 5 秒,表明 Kafka 每 5 秒会为你自动提交一次位移。

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");    
props.put("enable.auto.commit", "true"); 
props.put("auto.commit.interval.ms", "2000"); 
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserialize"); 
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserize");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);     
consumer.subscribe(Arrays.asList("foo", "bar")); 
while (true) {         
	ConsumerRecords<String, String> records = consumer.poll(100);
	for (ConsumerRecord<String, String> record : records){  
	    System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(),      
	}
}

  和自动提交相反的,就是手动提交了。开启手动提交位移的方法就是设置enable.auto.commit 为 false。但是,仅仅设置它为 false 还不够,因为你只是告诉Kafka Consumer 不要自动提交位移而已,你还需要调用相应的 API 手动提交位移。最简单的 API 就是KafkaConsumer#commitSync()。该方法会提交KafkaConsumer#poll() 返回的最新位移。从名字上来看,它是一个同步操作,即该方法会一直等待,直到位移被成功提交才会返回。如果提交过程中出现异常,该方法会将异常信息抛出。它也有一个缺陷,就是在调用 commitSync() 时,Consumer 程序会处于阻塞状态,直到远端的 Broker 返回提交结果,这个状态才会结束。下面这段代码展示了 commitSync() 的使用方法:

	while (true) {            
		ConsumerRecords<String, String> records =                        consumer.poll(Duration.ofSeconds(1));            
		process(records); 
		// 处理消息           
		try {                       
	    	consumer.commitSync();            
	    } catch (CommitFailedException e) { 
	    	handle(e); 
	    	// 处理提交失败异常           
	     }
	}

  相对于同步提交,还有相应的异步调用,调用 commitAsync() 之后,它会立即返回,不会阻塞,因此不会影响 Consumer 应用的 TPS。由于它是异步的,Kafka 提供了回调函数(callback),供你实现提交之后的逻辑,比如记录日志或处理异常等。

  commitAsync 是否能够替代 commitSync 呢?答案是不能。commitAsync 的问题在于,出现问题时它不会自动重试。因为它是异步操作,倘若提交失败后自动重试,那么它重试时提交的位移值可能早已经“过期”或不是最新值了。因此,异步提交的重试其实没有意义,所以 commitAsync 是不会重试的。

  1. 我们可以利用 commitSync 的自动重试来规避那些瞬时错误,比如网络的瞬时抖动,Broker 端 GC 等。因为这些问题都是短暂的,自动重试通常都会成功,因此,我们不想自己重试,而是希望 Kafka Consumer 帮我们做这件事。
  2. 我们不希望程序总处于阻塞状态,影响 TPS。
 try {while (true) {  
     ConsumerRecords<String, String> records =                                     consumer.poll(Duration.ofSeconds(1));                        
     process(records);
     // 处理消息,使用异步提交规避阻塞                       
     commitAysnc();           
     }
} catch (Exception e) {    
	// 处理异常        
	handle(e); 
	// 最后一次提交使用同步阻塞式提交
	consumer.commitSync(); 
} finally {  
	consumer.close();	
}

  如果polI()函数每次拉取数量过大,一旦中间出现差错,之前处理的全部都要重来一遍。如何分批提交呢?Kafka Consumer API 为手动提交提供了这样的方法:commitSync(Map<TopicPartition, OffsetAndMetadata>) 和commitAsync(Map<TopicPartition, OffsetAndMetadata>)。它们的参数是一个 Map对象,键就是 TopicPartition,即消费的分区,而值是一个 OffsetAndMetadata 对象,保存的主要是位移数据。

private Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();
int count = 0;
while (true) {            
	ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1)); 
	for (ConsumerRecord<String, String> record: records) {
		// 处理消息 
	    process(record);                         
	    offsets.put(new TopicPartition(record.topic(), record.partition),new OffsetAndMetadata(record.offset() + 1)if(count % 100 == 0{     
	     	// 回调处理逻辑是                                                       
	   		consumer.commitAsync(offsets, null);count++;
	   	}
	}
}

在这里插入图片描述

二、CommitFailedException异常怎么处理?

所谓 CommitFailedException,顾名思义就是 Consumer 客户端在提交位移时出现了错误或异常,而且还是那种不可恢复的严重异常。出现这个情况的原因是,你的消费者实例连续两次调用 poll 方法的时间间隔超过了期望的 max.poll.interval.ms 参数值。这通常表明,你的消费者实例花费了太长的时间进行消息处理,耽误了调用 poll 方法。

如果要防止这种场景下抛出异常,你需要简化你的消息处理逻辑。具体来说有 4 种方法。

  1. 缩短单条消息处理的时间。比如,之前下游系统消费一条消息的时间是 100 毫秒,优化之后成功地下降到 50 毫秒,那么此时 Consumer 端的 TPS 就提升了一倍。
  2. 增加 Consumer 端允许下游系统消费一批消息的最大时长。这取决于 Consumer 端参数 max.poll.interval.ms 的值。在最新版的 Kafka 中,该参数的默认值是 5 分钟。如果你的消费逻辑不能简化,那么提高该参数值是一个不错的办法。值得一提的是,Kafka0.10.1.0 之前的版本是没有这个参数的,因此如果你依然在使用 0.10.1.0 之前的客户端API,那么你需要增加 session.timeout.ms 参数的值。不幸的是,session.timeout.ms参数还有其他的含义,因此增加该参数的值可能会有其他方面的“不良影响”,这也是社区在 0.10.1.0 版本引入 max.poll.interval.ms 参数,将这部分含义从session.timeout.ms 中剥离出来的原因之一。
  3. 减少下游系统一次性消费的消息总数。这取决于 Consumer 端参数 max.poll.records的值。当前该参数的默认值是 500 条,表明调用一次 KafkaConsumer.poll 方法,最多返回 500 条消息。可以说,该参数规定了单次 poll 方法能够返回的消息总数的上限。如果前两种方法对你都不适用的话,降低此参数值是避免 CommitFailedException 异常最简单的手段。
  4. 下游系统使用多线程来加速消费。这应该算是“最高级”同时也是最难实现的解决办法了。具体的思路就是,让下游系统手动创建多个消费线程处理 poll 方法返回的一批消息。之前你使用 Kafka Consumer 消费数据更多是单线程的,所以当消费速度无法匹及Kafka Consumer 消息返回的速度时,它就会抛出 CommitFailedException 异常。如果是多线程,你就可以灵活地控制线程数量,随时调整消费承载能力,再配以目前多核的硬件条件,该方法可谓是防止 CommitFailedException 最高档的解决之道。事实上,很多主流的大数据流处理框架使用的都是这个方法,比如 Apache Flink 在集成Kafka 时,就是创建了多个 KafkaConsumerThread 线程,自行处理多线程间的数据消费。不过,凡事有利就有弊,这个方法实现起来并不容易,特别是在多个线程间如何处理位移提交这个问题上,更是极容易出错。在专栏后面的内容中,我将着重和你讨论一下多线程消费的实现方案。

引用:极客时间读书笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Kafka 是一个分布式的流平台,多个消费者可以同时订阅一个主题,并且每个消费者可以独立地读取主题中的消息。在 Kafka 中,消费者可以选择自动提交偏移量(offset),也可以选择手动提交偏移量。 手动提交偏移量意味着消费者负责跟踪已经处理的消息的偏移量,并且在适当的时候手动提交Kafka 集群。手动提交偏移量可以带来更好的控制,消费者可以根据业务需求决定何时提交、如何提交以及提交的频率。 在 Kafka 中,有两种手动提交的方式:同步提交和异步提交。 同步提交是指消费者在处理完一批消息后,等待提交请求完成后再继续下一批消息的处理。这种方式虽然保证了提交的可靠性,但会导致额外的等待时间,降低了消费者的吞吐量。 异步提交是指消费者在处理完一批消息后,立即发起提交请求,但并不等待请求完成就继续处理下一批消息。这种方式能够提高消费者的吞吐量,但在网络或服务故障时可能会导致提交失败,需要进行异常处理。 为了保证消费者在发生故障时不会丢失已经处理的消息和已经提交的偏移量,可以通过设置消费者的 auto.commit.offset 参数,将偏移量保存在 Kafka 服务端。这样,在消费者重新启动时,可以从上次提交的偏移量开始继续消费消息。 总的来说,手动提交偏移量可以提供更好的控制和可靠性,但需要消费者自己管理偏移量和处理提交请求的逻辑。消费者需要根据自身的需求选择适合的提交方式,并进行适当的异常处理,以保证消息的完整性和正确性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值