12、Kafka中位移提交那些事儿

Kafka中位移提交那些事儿

Consumer 端有个位移的概念,它和消息在分区中的位移不是一回事儿,虽然它们的英文都是 Offset。今天我们要聊的位移是 Consumer 的消费位移,它记录了 Consumer 要消费的下一条消息的位移。这可能和你以前了解的有些出入,不过切记是下一条消息的位移,而不是目前最新消费消息的位移。

举个例子。假设一个分区中有10条消息,位移分别是0到9。某个Consumer应用已消费了5条消息,这就说明该Consumer消费了位移为0到4的5条消息,此时Consumer的位移是5,指向了下一条消息的位移。

Consumer 需要向 Kafka 汇报自己的位移数据,这个汇报过程被称为提交位移 (Committing Offsets)。因为 Consumer 能够同时消费多个分区的数据,所以位移的提交实际上是在分区粒度上进行的,即 Consumer 需要为分配给它的每个分区提交各自的位移数据

提交位移主要是为了表征 Consumer 的消费进度,这样当 Consumer 发生故障重启之后,就能够从 Kafka 中读取之前提交的位移值,然后从相应的位移处继续消费,从而避免整个消费过程重来一遍。换句话说,位移提交是 Kafka 提供给你的一个工具或语义保障,你负责维持这个语义保障,即如果你提交了位移 X,那么 Kafka 会认为所有位移值小于X的消息你都已经成功消费了。

这一点特别关键。因为位移提交非常灵活,你完全可以提交任何位移值,但由此产生的后果你也要一并承担。

位移提交的语义保障是由你来负责的,Kafka只会"无脑"地接受你提交的位移。

鉴于位移提交甚至是位移管理对Consumer端的巨大影响,Kafka,特别是KafkaConsumer API,提供了多种提交位移的方法。从用户的角度来说,位移提交分为自动提交和手动提交;从Consumer端的角度来说,位移提交分为同步提交和异步提交。

1、自动提交位移

把参数 enable.auto.commit 设置为 true。

一旦设置了 enable.auto.commit 为 true,Kafka 会保证在开始拉取消息时,提交上次 poll 返回的所有消息。从顺序上来说,poll 方法的逻辑是先提交上一批消息的位移,再处理下一批消息,因此它能保证不出现消费丢失的情况。但自动提交位移的一个问题在于,它可能会出现重复消费

在默认情况下,Consumer 每 5 秒自动提交一次位移。现在,我们假设提交位移之后的 3 秒发生了 Rebalance 操作。在 Rebalance 之后,所有 Consumer 从上一次提交的位移处继续消费,但该位移已经是 3 秒前的位移数据了,故在 Rebalance 发生前 3 秒消费的所有数据都要重新再消费一次。虽然你能够通过减少 auto.commit.interval.ms 的值来提高提交频率,但这么做只能缩小重复消费的时间窗口,不可能完全消除它。这是自动提交机制的一个缺陷。

2、手动提交位移

首先设置 enable.auto.commit 为 false,然后调用相应的 API 手动提交位移。

2.1、同步提交位移

最简单的 API 就是 KafkaConsumer#commitSync()。该方法会提交 KafkaConsumer#poll() 返回的最新位移。从名字上来看,它是一个同步操作,即该方法会一直等待,直到位移被成功提交才会返回。如果提交过程中出现异常,该方法会将异常信息抛出。下面这段代码展示了 commitSync() 的使用方法:

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

手动提交位移,它的好处就在于更加灵活,你完全能够把控位移提交的时机和频率。但是,它也有一个缺陷,就是在调用 commitSync() 时,Consumer 程序会处于阻塞状态,直到远端的 Broker 返回提交结果,这个状态才会结束。在任何系统中,因为程序而非资源限制而导致的阻塞都可能是系统的瓶颈,会影响整个应用程序的 TPS。当然,你可以选择拉长提交间隔,但这样做的后果是 Consumer 的提交频率下降,在下次 Consumer 重启回来后,会有更多的消息被重新消费。

2.2、异步提交位移

鉴于同步提交位移的阻塞问题,Kafka 社区为手动提交位移提供了另一个 API 方法: KafkaConsumer#commitAsync()。从名字上来看它就不是同步的,而是一个异步操作。调用 commitAsync() 之后,它会立即返回,不会阻塞,因此不会影响 Consumer 应用的 TPS。由于它是异步的,Kafka 提供了回调函数(callback),供你实现提交之后的逻辑,比如记录日志或处理异常等。

java 复制代码
while (true) {
	ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
	process(records); // 处理消息
	consumer.commitAsync((offsets, exception) -> {
		if (exception != null)
			handle(exception);
	});
}

2.3、更精细化的位移管理

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

显然,如果是手动提交,我们需要将 commitSync 和 commitAsync 组合使用才能达到最理想的效果,原因有两个:

  1. 我们可以利用 commitSync 的自动重试来规避那些瞬时错误,比如网络的瞬时抖动,Broker 端 GC 等。因为这些问题都是短暂的,自动重试通常都会成功,因此,我们不想自己重试,而是希望 Kafka Consumer 帮我们做这件事。
  2. 我们不希望程序总处于阻塞状态,影响 TPS。

我们来看一下下面这段代码,它展示的是如何将两个API方法结合使用进行手动提交。

java 复制代码
try{
    while(true) {
         ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
         process(records); // 处理消息
         commitAysnc(); // 使用异步提交规避阻塞
     }
} catch(Exception e) {
           handle(e); // 处理异常
} finally {
    try {
  	   consumer.commitSync(); // 最后一次提交使用同步阻塞式提交
	} finally {
	   consumer.close();
	}
}

这段代码同时使用了 commitSync() 和 commitAsync()。对于常规性、阶段性的手动提交,我们调用 commitAsync() 避免程序阻塞,而在 Consumer 要关闭前,我们调用 commitSync() 方法执行同步阻塞式的位移提交,以确保 Consumer 关闭前能够保存正确的位移数据。将两者结合后,我们既实现了异步无阻塞式的位移管理,也确保了 Consumer 位移的正确性,所以,如果你需要自行编写代码开发一套 Kafka Consumer 应用,那么我推荐你使用上面的代码范例来实现手动的位移提交。

相关推荐
python资深爱好者14 分钟前
什么容错性以及Spark Streaming如何保证容错性
大数据·分布式·spark
HeartRaindj1 小时前
【中间件开发】kafka使用场景与设计原理
分布式·中间件·kafka
明达技术3 小时前
探索分布式 IO 模块网络适配器
分布式
Ray.19983 小时前
优化 Flink 消费 Kafka 数据的速度:实战指南
大数据·flink·kafka
爬山算法4 小时前
Zookeeper(58)如何在Zookeeper中实现分布式锁?
分布式·zookeeper·云原生
明达技术6 小时前
分布式 IO 模块:造纸设备的降本增效利器
分布式
一位卑微的码农10 小时前
深入解析Spring Cloud Config:构建高可用分布式配置中心
分布式·spring cloud·微服务·架构
Bai_Yin12 小时前
Debezium 与 Apache Kafka 的集成方式
分布式·kafka·apache·debezium
劉煥平CHN12 小时前
RabbitMQ的脑裂(网络分区)问题
网络·分布式·rabbitmq
明达技术12 小时前
分布式 IO 模块:水力发电设备高效控制的关键
分布式