Kafka 是一个高度可扩展的分布式消息系统,在实时事件流和流式处理为中心的架构越来越风靡的今天,它扮演了这个架构中核心存储的角色。从某种角度说,Kafka 可以看成实时版的 Hadoop 系统。Hadoop 可以存储和定期处理大量的数据文件,而 Kafka 可以存储和持续处理大型的数据流。

Hadoop 和文件系统提供文件流的读取位点( offset ),并支持通过 seek 方法将文件流移动到特定位置;Kafka 对应的提供了主题下每个分区的消费位点( offset ),并允许消费者设置分区的读取位置。本文首先介绍 Kafka 消费者消费消息的方式,随后回答 Kafka 如何管理消费位点这一元数据的问题。后面一个主题包括 Kafka 如何提交以及设置消费位点的实现,这是 Kafka 为应用系统提供可靠性保障的重要组成部分

Kafka 消费者的消费模式

Kafka 的数据由主题和分区划分。应用程序使用 KafkaConsumer 向 Kafka 订阅主题,并从订阅的主题上接受消息,订阅主题的模板代码如下所示。

consumer.subscribe(Collections.singletonList("customTopic"));

可以看到,我们为每个消费者指定了它所消费的主题。

在分布式系统的语境下,当生产者通过水平扩展提高了整体主题写入消息的速度时,单个消费者很快就跟不上消息生产的速度。直观地,我们想要通过同样地水平扩展手段,使用多个消费者来分摊消息消费的压力。

Kafka 利用消费组的概念来支持消费者的水平扩展。消费者从属于消费组,消费组的消费者订阅同一个主题,每个消费者接受主题的一部分分区的消息。消费者通过创建时的 group.id 指定它所从属的消费组。

消费者加入消费组或离开消费组会引起消费组所消费的主题的分区在组内消费者之间的再均衡( rebalance )。消息的再均衡在流式处理的范畴里是一个复杂的话题,本文不讨论其细节,假设每个消费者都稳定地消费主题的若干个分区。

在 Kafka 与某些系统的整合里,消费者消费的分区是由外部系统所指定和协调的,Kafka 为了支持这样的场景提供了主动为消费者分配分区的接口。

consumer.assign(Collections.singletonList(new TopicPartition("customTopic", 1)));

当消费者指定了自己所要消费的主题和分区后,应用程序通过消息轮询来与 Kafka 集群交互并请求数据进行消费。Kafka 在轮询中进行很多操作,包括消费组协调、分区再均衡和获取数据。在这里我们主要关心获取数据进行消费的情况,模板代码如下所示。

try {
while (true) {
for (ConsumerRecord<String, String> record : consumer.poll(Duration.ofMills(1000L))) {
// do something with record...
}
}
} finally {
consumer.close();
}

可以看到,应用程序通常在一个无限循环里通过轮询来消费 Kafka 里的数据。当消费者缓冲区有数据或 poll 最长的阻塞时间到达时,将返回本次轮询取得的消息集合。

返回的消息集合的元素包括消息所属主题的信息、所在分区的信息、所在分区的消费位点以及消息数据即其键值对。通常,我们遍历消息集合来处理轮询取得的消息。

最后,在 finally 块中我们调用了消费者的 close 方法,从而显式地关闭消费者,并关闭网络连接。这个操作同时会触发一次消费组的再均衡,从而避免必须等待消费组协调者在该消费者心跳超时后才发现其离开消费者并触发再均衡。

Kafka 如何提交消费位点

消费者每次调用 poll 方法总是返回由生产者写入 Kafka 但是还没有被消费者消费的消息,那么 Kafka 是怎么定位哪些消息还没被消费者消费的呢?

答案就是消费位点。

Kafka 通过消费位点来追踪消息在分区里的消费进度,而不需要强制对每个消息都进行确认。我们把更新分区消费位点的操作叫做提交( commit )。

Kafka 追踪消费位点的方式充分利用了 Kafka 自身的能力,通过向 Kafka 内部名为 __consumer_offsets 的主题发送包装了消费位点信息的消息来保存消费位点。消费者正常运行时,还会在内存中为每个分配的分区记录一个获取数据的数据位点。

因此,如果消费者一直正常运行,持久化在 __consumer_offsets 主题的消费位点元数据用处不大,因为消费者会自己追踪消费位点。但是在有的消费者发生崩溃重启或者主题分区发生再均衡时,重启的分区需要恢复丢失的内存中的消费位点信息,或者再均衡后的消费者接手新的分区的情形下,消费者就需要读取分区最后一次提交的消费位点,以从该消费位点继续往下消费数据。

在这种情形下,如果提交的消费位点小于应用程序消费者实际曾经处理过的最后一个消息的消费位点,那么这两点之间的消息就会被重复处理。反之,如果提交的消费位点大于应用程序消费者实际曾经处理过的最后一个消息的消费位点,那么这两点之间的消息就会被跳过,不被处理。

因此,为了提高应用程序处理消息的可靠性,Kafka 提供了若干种提交消费位点的方式,以支持应用程序根据自身逻辑提交尽可能准确的消费位点。

自动提交

简单的 Kafka 消费应用程序可以采用自动提交的手段让消费者自动提交消费位点。只要在创建消费者的时候将 enable.auto.commit 配置设置为 true 值,那么消费者就会在 poll 方法里在拉取新的消息之前自动提交当前的消费位点。决定自动提交周期的是 auto.commit.interval.ms 配置,默认是 5 秒,即每过 5 秒,在下一次 poll 时自动提交。

自动提交虽然方便,但是一切自动的行为,使用者都需要小心的确认其行为并了解它在极端情况下的表现。

一个典型的自动提交的边界场景是分区再均衡场景。假设我们采用默认 5 秒的自动提交时间间隔,在本分区最近一次提交后 3 秒发生了故障,再均衡之后,新的消费者从本分区的消费位点开始读取并处理消息。由于消费位点是故障前 3 秒前自动提交的,在这 3 秒之间读取的消息及其影响的消费位点没有被提交,因此这些数据将被重复处理。

可以通过缩短自动提交的间隔来减小重复数据的时间窗口,但是重复数据在理论上是不可避免的。此外,频繁的提交将带来额外的调度开销和通信开销。

另一个值得注意的是,自动提交的配置下,每一次 poll 调用都会提交上一次 poll 移动到的消费位点,在调用消费者的 close 方法时也会触发自动提交。通常来说,自动提交的消费位点总是不大于消费者实际处理的消息。但是,如果在轮询时拉取到一批消息,并在处理完所有消息之前抛出异常,就有可能导致自动提交时按照这批消息处理过的假设进行提交,从而导致部分消息被跳过,不被处理的情形。

主动提交

对于定制 Kafka 消费逻辑的应用,或者说整合 Kafka 到更大的流式处理系统的场景,主动提交当前的消费位点是一个必须的功能。应用程序或者复合系统通过控制消费位点的提交时间来消除消息丢失的可能性,并在发生再均衡时减少重复消息的数量。

首先,这要求把前面提到的 enable.auto.commit 配置设置为 false 值。随后,通过调用消费者的提交消费位点的接口来进行主动提交。Kafka 提供的接口包括以下几种。

void commitSync();
void commitSync(Map<TopicPartition, OffsetAndMetadata> offsets);
void commitAsync();
void commitAsync(OffsetCommitCallback callback);
void commitAsync(Map<TopicPartition, OffsetAndMetadata> offsets, OffsetCommitCallback callback);

可以看到,这些接口一方面分为同步和异步两类,另一方面分为是否带有 offsets 参数两类。

显然,同步的提交会阻塞应用程序的运行,默认情况下这个阻塞的超时时间是由 default.api.timeout.ms 配置决定的,默认为 1 分钟。在早期的版本中,这个时间是无限的,即应用程序会永远阻塞并无限重试直到抛出不可恢复的异常或成功提交。

异步的提交则会将提交动作放在异步线程完成,并支持传入可选的 OffsetCommitCallback 来定制提交动作成功后的回调逻辑。异步的提交的异步性在于消费者向 Kafka 集群发送提交消费位点的请求后,不阻塞等待集群的返回,而是注册一个集群返回时的回调来响应成功或者失败的事件。

由于发送请求是同步的,而且 Kafka 底层采用 TCP 进行通信,因此我们可以认为异步的消费位点提交也是顺序发生在 Kafka 集群的。然而,如果在提交失败的情形下消费者通过 Callback 尝试重新提交,就必须注意重试可能导致提交一个更早的消费位点从而在再平衡场景下导致不必要的重复消费的情况。

到现在我们所讨论的接口都是不带 offsets 参数的,在这种情况下,Kafka 会自动获取当前消费者内存所保存的消费位点来进行提交。对于某些定制化的消费位点管理逻辑,可以通过传入 offsets 参数来自定义需要提交的消费位点的内容。offsets 参数是一个主题及分区锁定具体分区的键和将要提交的该分区的消费位点及元数据的值组成的映射。

集群交互

上面介绍的提交消费位点的技术都是从消费者角度看那些接口参与实现这个功能的。对于想深入理解这一过程的同学,这里简要介绍一下相关的逻辑在源代码的位置及相应的逻辑线。

在消费者一侧的底层逻辑,上面自动提交和主动提交的逻辑最终都由 ConsumerCoordicator 类来执行,对应的接口定义如下。

class ConsumerCoordinator {
void maybeAutoCommitOffsetsAsync(long now);
boolean commitOffsetsSync(Map<TopicPartition, OffsetAndMetadata> offsets, Timer timer);
void commitOffsetsAsync(Map<TopicPartition, OffsetAndMetadata> offsets, final OffsetCommitCallback callback);
}

这些方法在底层都会首先查找当前消费者对应的消费组协调者,即 Kafka 集群中的某个服务器,随后向它发起 OffsetCommitRequest 请求,该请求包含了服务器向 __consumer_offsets 主题写入消费位点所需要的所有信息。

消费组协调者通过 SocketServer 组件接受到请求后,反序列化请求并交给 KafkaApis 组件处理请求。KafkaApis 是 Kafka 服务器所有业务逻辑的聚合类。它识别出 OffsetCommitRequest 后转发到 handleOffsetCommitRequest 方法进行处理。在一系列的参数检查之后,主流程的消息处理最终将由 GroupCoordinator#handleCommitOffsets 处理。

Kafka 如何设置消费位点

现在,我们知道了 Kafka 提交消费位点的方式,并且知道了持久化到 Kafka 集群的消费位点通常在消费者崩溃或者集群发生再均衡的时候被读取和使用。但是,同样是在某些定制场景下,Kafka 消费者的消费位点是由外部系统维护的。

在这种情况下,Kafka 也支持从特定的消费位点开始处理消息,对应的接口定义如下。

void seekToBeginning(Collection<TopicPartition> partitions);
void seekToEnd(Collection<TopicPartition> partitions);
void seek(TopicPartition partition, long offset);
void seek(TopicPartition partition, OffsetAndMetadata offsetAndMetadata);

其中 seekToBeginning 方法将分区的消费位点回拨到分区的起始位置开始读取消息,而 seekToEnd 方法将分区的消费位点跳到分区的末尾开始读取信息。显然,前者将会导致大量的重复消息处理,而后者将带来跳过某些消息不做处理的风险。

在本文的开头我们提到,Hadoop 和文件系统提供文件流的读取位点,并支持通过 seek 方法将文件流移动到特定位置。Kafka 同样支持 seek 方法来设置消息的消费位点,从新的消费位点开始消费数据。

从实现上说,设置消费位点是一个消费者的本地操作。它直接改动了订阅状态下主题分区状态的消费位点消息,从而在下一次 poll 方法调用时从新设置的消费位点开始向 Kafka 集群拉取消息进行消费。

这一功能的实际应用场景包括应用程序需要保证某种程度的数据可靠性的情形。

通常,数据的消费者在接收到 Kafka 消息之后会进行相应的处理并生成新的数据,典型场景下这一数据将被持久化到数据库中或者进入到下一阶段的流式处理系统里。如果数据保存在数据库里或进入其他系统之中,而消费位点提交到 Kafka 上,这样多个系统之间天然的异步性将使原子提交的操作成为不可能。

但是,如果在同一个事务里将数据和消费位点都写到数据库里,或者进入到流式处理系统里追踪起来,我们就可以保证这两者是原子地被提交或者失败。此时,消费位点保存在 Kafka 系统之外,因此需要上面的 seek 方法来主动设置消费位点以告诉消费者从什么位置开始读取并消费数据。

另外两个方法跟 auto.offset.reset 配置的 ealiest 和 latest 选项相对应,常作为主题分区消费位点不存在时采用的兜底设置消费位点的方案。

Kafka 是如何管理消费位点的?的更多相关文章

  1. Kafka集群管理工具kafka-manager的安装使用

    一.kafka-manager简介 kafka-manager是目前最受欢迎的kafka集群管理工具,最早由雅虎开源,用户可以在Web界面执行一些简单的集群管理操作.具体支持以下内容: 管理多个集群 ...

  2. Linux Kafka集群管理工具kafka-manager的安装使用

    一.kafka-manager简介 kafka-manager是目前最受欢迎的kafka集群管理工具,最早由雅虎开源,用户可以在Web界面执行一些简单的集群管理操作.具体支持以下内容: 管理多个集群 ...

  3. 安装配置 Kafka Manager 分布式管理工具

    Kafka Manager 特性,它支持以下内容(官方译解): 管理多个群集容易检查集群状态(主题,消费者,偏移量,经纪人,副本分发,分区分配)运行首选副本选举使用选项生成分区分配,以选择要使用的代理 ...

  4. Kafka集群管理工具kafka-manager

    一.kafka-manager简介 kafka-manager是目前最受欢迎的kafka集群管理工具,最早由雅虎开源,用户可以在Web界面执行一些简单的集群管理操作.具体支持以下内容: 管理多个集群 ...

  5. kafka web端管理工具 kafka-manager【转发】

    1,下载与安装 $ git clone https://github.com/yahoo/kafka-manager.git $ cd kafka-manager $ ./sbt clean dist ...

  6. 【Java面试】Kafka 怎么避免重复消费

    Hi,大家好,我是Mic 一个工作5年的粉丝找到我. 他说: "Mic老师,你要是能回答出这个问题,我就佩服你" 我当场就懵了,现在打赌都这么随意了吗? 我问他问题是什么,他说&q ...

  7. 【SparkStreaming学习之四】 SparkStreaming+kafka管理消费offset

    环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 jdk1.8 scala-2.10.4(依赖jdk1.8) spark ...

  8. spark streaming - kafka updateStateByKey 统计用户消费金额

    场景 餐厅老板想要统计每个用户来他的店里总共消费了多少金额,我们可以使用updateStateByKey来实现 从kafka接收用户消费json数据,统计每分钟用户的消费情况,并且统计所有时间所有用户 ...

  9. Kafka的常用管理命令

    1. 查看kafka都有那些topic a. list/usr/hdp/current/kafka-broker/bin/kafka-topics.sh --list --zookeeper test ...

随机推荐

  1. Linux使用手册

    一.开关机 sync :把内存中的数据写到磁盘中(关机.重启前都需先执行sync) shutdown -rnow或reboot :立刻重启 shutdown -hnow :立刻关机 shutdown ...

  2. 这一次搞懂Spring事务是如何传播的

    文章目录 前言 正文 事务切面的调用过程 事务的传播性概念 实例分析 总结 前言 上一篇分析了事务注解的解析过程,本质上是将事务封装为切面加入到AOP的执行链中,因此会调用到MethodIncepto ...

  3. Python 为什么推荐蛇形命名法?

    关于变量的命名,这又是一个容易引发程序员论战的话题.如何命名才能更具有可读性.易写性与明义性呢?众说纷纭. 本期"Python为什么"栏目,我们将聚焦于变量命名中的连接方式,来切入 ...

  4. Centos7 composer安装时 Warning: This development build of composer is over 60 days old. It is recommended to update it by running "/usr/bin/composer self-update" to get the latest version.

    emmm,其实就是想让你运行一下/usr/bin/composer self-update这个命令更新一下

  5. 嵌入式QT开发视频教程-供参考

    免费嵌入式QT开发视频教程 https://pan.baidu.com/s/1bprhJ2Z QT初级到高级编程视频教程--丁林松.rarhttp://www.jisoupan.com/share/2 ...

  6. 跨云厂商部署 k3s 集群

    原文链接:https://fuckcloudnative.io/posts/deploy-k3s-cross-public-cloud/ 最近一两年各大云服务商都出了各种福利活动,很多小伙伴薅了一波又 ...

  7. Windows安装C的编译环境

    对于java开发者来说安装C的编译环境不是非常熟悉,因此本文对C的安装环境进行介绍以及windows编译Redis和Zookeeper的过程.MinGW主要用于按照gcc.make等环境,cywin用 ...

  8. java中“”==“” equals hashcode的关系

    ava中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号(==),比 ...

  9. 梳理搭建SSM步骤

    以上全程手撕,如有不足或错误的,请指正!

  10. idea 链接 Tomcat 出现的错误 Application server libraries not found

    红色字体是重点 好久好久没有上来了.一直忙于工作没事有时间进行总结. 最近在家隔离期间发现了一个好玩的游戏率土之滨  , 玩的不错和大盟主混的很好.在给盟里做考勤任务的时候发现很麻烦还需要用Excel ...