Kafka-Consumer理论知识

一、上下文

之前的博客我们分析了Kafka的设计思想、Kafka的Producer端、Kafka的Server端的分析,为了完整性,我们接下来分析下Kafka的Consumer。《Kafka-代码示例》中有对应的Consumer示例代码,我们以它为入口进行分析

二、KafkaConsumer是什么?

一个从Kafka集群中消费记录的客户端,KafkaConsumer与broker交互,会随着获取的TopicPartition在集群中因故障迁移而自己调整。

KafkaConsumer允许消费者组使用consumer groups对消费进行负载平衡

三、offset

Kafka为分区中的每条记录维护一个offset,该offset充当该分区内记录的唯一标识符,也表示consumer的消费进度。例如:如果offset=5就表示offset为0-4的记录已经被消费。

consumer的position(TopicPartition)给出了下一条记录的offset,这将比消费者在该分区中看到的最高偏移量大一个。每次consumer在调用poll(Duration) 收到消息时,都会推进offset的增长。

consumer可以交由 kafka 定期自动提交offset 。也可以 调用 commitSync() 手动提交。

如果consumer进程失败并重新启动,会从维护的offset处开始继续消费。

1、自动提交offset

如下的示例依赖于自动提交offset

Properties props = new Properties();
//指定broker列表,用于连接kafka集群,1个或多个都可以,尽量多个
props.setProperty("bootstrap.servers", "localhost:9092");
//定义的组为test
props.setProperty("group.id", "test");
//配置自动提交
props.setProperty("enable.auto.commit", "true");
//自动提交频率,1000表示1秒提交1次
props.setProperty("auto.commit.interval.ms", "1000");
props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
//订阅 foo bar 两个topic的数据
consumer.subscribe(Arrays.asList("foo", "bar"));
while (true) {ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));for (ConsumerRecord<String, String> record : records)System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}

 当程序正常运行,offset会随着consumer的消费自动向前推进,如果consumer发生故障,会出现重复消费或数据丢失的情况。

如果Kafka以auto.commit.interval.ms的频率提交了offset,consumer在下一次自动提交前出现故障,当consumer再次启动后会出现重复消费的现象。

如果consumer刚刚拉回来一批数据准备处理,此刻Kafka正好自动提交offset,但是consumer出现了故障,当consumer再次启动后会出现这批数据丢失的现象。

2、手动提交offset

如下的示例依赖于手动提交offset

Properties props = new Properties();
props.setProperty("bootstrap.servers", "localhost:9092");
props.setProperty("group.id", "test");
//关闭自动提交
props.setProperty("enable.auto.commit", "false");
props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("foo", "bar"));
final int minBatchSize = 200;
List<ConsumerRecord<String, String>> buffer = new ArrayList<>();
while (true) {ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));for (ConsumerRecord<String, String> record : records) {buffer.add(record);}if (buffer.size() >= minBatchSize) {insertIntoDb(buffer);//手动提交consumer.commitSync();buffer.clear();}
}

用户可以根据自己处理数据的逻辑,控制何时提交offset。

在这个例子中,我们需要积累一定数量的数据后再批量插入数据库中。因此只有插入数据库后才需要提交offset。

这里还会发生一个小概率事件:这批数据插入数据库成功,但是再手动提交offset时,consumer发生故障。这会导致consumer重启后发生重复消费现象。因此最好把插入数据和提交offset放入到一个事务中,要么都成功,要么都失败。

上面的示例使用 commitSync() 将所有接收到的记录标记为已提交。在某些情况下,可能希望通过明确指定offset来更好地控制已提交的记录。比如下面示例:

try {while(running) {ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(Long.MAX_VALUE));for (TopicPartition partition : records.partitions()) {List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);for (ConsumerRecord<String, String> record : partitionRecords) {System.out.println(record.offset() + ": " + record.value());}long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();//提交的偏移量应始终是应用程序将读取的下一条消息的偏移量//因此这里给的是lastOffset + 1consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1)));}}
} finally {consumer.close();
}

四、consumer groups

Kafka使用consumer groups的概念来允许一个进程池来划分消费和处理记录的工作。这些进程可以在同一台机器上运行,也可以分布在许多机器上,为处理提供可扩展性和容错性。

共享统一 group.id  的所有consumer 都将属于同一消费者组。组中的每个消费者都可以通过其中一个subscribe(Collection, ConsumerRebalanceListener) API动态设置要订阅的topic列表。Kafka会将订阅topic中的每条消息传递给每个消费者组中的一个进程。这是通过平衡消费者组中所有成员之间的分区来实现的,这样每个分区都被分配给组中的一个消费者。

如果一个topic有四个分区,一个消费者组有两个进程,那么每个进程会被分配两个分区来消费。

consumer group中的consumer是动态维护的,如果要给consumer出现故障,分配给它的分区将被重新分配给同一组中的其他consumer。同样,如果新的consumer加入该组,分区将从现有consumer移动到新的consumer。这被称 重新平衡 组。

当topic中的分区增多时也会触发 组的 重新平衡

当组重新分配自动发生时,可以通过ConsumerRebalanceListener通知消费者,这允许他们完成必要的应用程序级逻辑,如状态清理、手动偏移提交等。

消费者也可以使用assign()手动分配特定分区。在这种情况下,动态分区分配和消费者组协调将被禁用。

五、监测consumer故障

consumer订阅了一组topic后,当调用poll(Duration)  时,消费者将自动加入一个组。poll(Duration) 可以让Kafka认为该consumer是活动状态,consumer就会留在组中,并继续从分配给它的分区接收消息。

consumer定期向服务器发送心跳,如果consumer崩溃或在 session.timeout.ms  的持续时间内无法发送心跳,则consumer将被视为已死亡,其分区将被重新分配。

consumer也可能遇到“livelock”情况,即它继续发送心跳,但没有调用poll(Duration)

为了防止consumer在这种情况下无限期地保留其分区,Kafka使用 max.poll.interval.ms 设置提供了一种活性检测机制。如果consumer在max.poll.interval.ms内没有执行poll(Duration)消费数据,那么consumer将主动离开该组,以便另一个consumer可以接管其分区。

当这种情况发生时,可能会看到偏移提交失败(如调用commitSync() 时抛出的CommitFailedException 异常。这是一种安全机制,保证只有组中的活跃成员才能提交偏移。因此,要想留在组中,就必须持续调用 poll(Duration)

consumer提供两个配置来控制轮询循环的行为:

max.poll.interval.ms:

        增加轮询之间的间隔,让consumer可以有更多的事件来处理拉取的数据,缺点:增加此值可能会延迟组重新平衡。

max.poll.records:

        使用此设置可限制单次轮询调用返回的总记录数,通过调整此值,您可以减少轮询间隔,这将减少组重新平衡的影响

如果拉回的消息处理时间变化不可预测,这两种选项可能都不能完美的解决。处理这种情况的推荐方法是将消息处理转移到另一个线程或者线程池。此consumer只负责拉取数据。如果使用这种方法,需要关闭自动提交offset,当数据处理完成进行手动提交offset。

六、为consumer手动指定分区

一般情况下,consumer只要订阅topic或topic列表,并让Kafka根据组中的活动consumer为这些主题动态分配公平的分区份额。但某些情况下,可能需要对特定分区进行更精细的控制。

1、如果进程正在维护与该分区相关联的某种本地状态(如本地磁盘键值存储),那么它应该只获取它在磁盘上维护的分区的记录。

2、如果进程本身具有高可用性,并且在失败时将重新启动(可能使用YARN、Mesos或AWS设施等集群管理框架,或者作为流处理框架的一部分)。在这种情况下,Kafka不需要检测故障并重新分配分区,因为消费进程将在另一台机器上重新启动。

使用示例:

String topic = "foo";
TopicPartition partition0 = new TopicPartition(topic, 0);
TopicPartition partition1 = new TopicPartition(topic, 1);
consumer.assign(Arrays.asList(partition0, partition1));

手动分区分配不使用组协调,因此消费者故障不会导致分配的分区重新平衡。每个消费者独立行动,即使它与另一个消费者共享一个groupId。为了避免偏移提交冲突,通常应该确保每个消费者实例的groupId都是唯一的。

请注意:不可能将手动分区分配和订阅topic混合使用。

七、将offset维护转移到Kafka外部

Kafka自身会维护一个内部topic,用于存储offset。当然我们也可以选择将offset存储在外部存储系统,例如mysql。也可以将消费结果和offset存放在同一个存储系统,以原子方式存储结果和偏移量(用数据库事务 将 数据消费和offset提交绑定在一起)。这将使消费完全原子化,并给出“恰好一次”的语义,该语义比您使用Kafka的偏移提交功能获得的默认“至少一次”语义更强。

此时的做法是:

1、配置手动提交 enable.auto.commit=false

2、使用每个 ConsumerRecord提供的偏移量来保存您的位置

3、重新启动时,使用seek(TopicPartition, long)恢复消费者的位置

如果consumer手动指定分区,那么将offset维护到外部是简单的,因为指定的这个分区一直都是归属与你这个consumer来消费的。如果consumer消费的分区时Kafka给你自动分配的,则会因为consumer的变化等因素导致你当下的consumer被分到的分区变化。这时,需要通过对subscribe(Collection, ConsumerRebalanceListener)和subscribe(Pattern, ConsumerRebalanceListener)的调用提供一个ConsumerRebalanceListener

例如:当consumer获取分区时,consumer希望通过实现ConsumerRebalanceListener.onPartitionsRevoked(Collection)来提交这些分区的偏移量。当将分区分配给消费者时,消费者将希望查找这些新分区的偏移量,并通过实现ConsumerRebalanceListener.onPartitionsAssigned(Collection)将consumer正确初始化到该位置。

ConsumerRebalanceListener的另一个常见用途是清除应用程序为移动到其他地方的分区维护的任何缓存

八、控制consumer的消费位置

在大多数用例中,consumer只需从头到尾消费记录,定期提交其位置(自动或手动)。然而,Kafka允许consumer手动控制其位置,在分区中随意向前或向后移动。这意味着consumer可以重新消费旧记录,或者跳到最近的记录,而无需实际消费中间记录。

例如对于时间敏感的记录处理,如果数据已经生产了很长时间,对于一个新的consumer来说就很有意义。它可以直接处理最新的数据,或者它只关心中午12点之后的数据。

九、流量控制

如果一个consumer被分配了多个分区来获取数据,它将尝试同时从所有分区中消费,从而有效地赋予这些分区相同的消费优先级。然而,在某些情况下,consumer可能希望首先全速处理某一个分区。当这个分区没有数据时才处理其他分区。

还有一种情况,那就是流处理,一个consumer从两个topic消费数据,并对这两个流进行连接。当其中一个topic远远落后与另一个topic时,consumer会暂停从速度快的topic获取数据,以便让滞后的topic跟上。

Kafka支持消费流的动态控制,在未来的 poll(Duration)调用中,分别使用pause(Collection)和resume()来暂停指定分配分区上的消费和恢复指定暂停分区上的消费。

十、消费事务性消息

Kafka 0.11.0引入了事务,其中producer可以原子地写入多个topic和partition。为了实现这一点,从这些分区读取的consumer应该被配置为只读取已提交的数据。既:isolation.level=read_committed

在read_committed模式下,consumer将只读取已成功提交的事务消息。consumer的分区结束偏移量将是分区中属于打开事务的第一条消息的偏移量。这种偏移被称为“最后稳定偏移”(LSO)

LSO 是 Last Stable Offset 的缩写

consumer只会读取LSO并过滤掉任何已中止的事务消息。LSO还影响consumer的seekToEnd(Collection)和endOffsets(Collection)行为。

带有事务消息的分区将包括表示事务结果的提交或中止标记。这些标记不会返回给consumer,但在日志中有偏移。

因此,从具有事务性消息的topic中读取的应用程序将看到所消耗的偏移量存在缺口。这些缺失的消息将成为事务标记,并在两个隔离级别中为consumer过滤掉。此外,消费者也可能会看到由于事务中止而导致的空白,因为这些消息不会被consumer返回,但会有有效的偏移量。

十一、多线程消费

Kafka消费者不是线程安全的。我们有责任确保多线程访问正确同步。不同步访问将导致ConcurrentModificationException

此规则的唯一例外是 wakeup(),它可以从外部线程安全地用于中断活动操作。在这种情况下,将从操作的线程阻塞中抛出org.apache.kafka.common.errors.WakeupException这可用于从另一个线程关闭消费者。

如下示例:

public class KafkaConsumerRunner implements Runnable {private final AtomicBoolean closed = new AtomicBoolean(false);private final KafkaConsumer consumer;public KafkaConsumerRunner(KafkaConsumer consumer) {this.consumer = consumer;}{@literal}@Overridepublic void run() {try {consumer.subscribe(Arrays.asList("topic"));while (!closed.get()) {ConsumerRecords records = consumer.poll(Duration.ofMillis(10000));// 处理新记录 ......}} catch (WakeupException e) {// 如果关闭,忽略异常if (!closed.get()) throw e;} finally {consumer.close();}}// Shutdown hook which can be called from a separate thread// 关闭钩子,可以从单独的线程调用public void shutdown() {closed.set(true);consumer.wakeup();}
}

然后在一个单独的线程中,可以通过设置closed标志并唤醒消费者来关闭消费者。

closed.set(true);
consumer.wakeup();

我们看以下几种情况:

1、1个consumer一个线程

优点:

1、最容易实施

2、它通常是最快的,因为不需要线程间的协调

3、它使得基于每个分区的按顺序处理非常容易实现(每个线程只按接收消息的顺序处理消息)。

缺点:

1、更多的consumer意味着更多的TCP连接到集群(每个线程一个)。一般来说,Kafka非常有效地处理连接,因此这通常是一个很小的成本。

2、多个consumer意味着向服务器发送的请求更多,数据批处理略有减少,这可能会导致I/O吞吐量下降。

3、所有进程中的线程总数将受到分区总数的限制

2、将消费和处理解耦

consumer只负责接收数据,处理数据的逻辑让另一个线程池来处理

优点:

        此选项允许独立扩展consumer和处理器的数量。这使得有可能有一个为多个处理器线程提供数据的单一consumer,从而避免了对分区的任何限制。

缺点:

        1、保证处理器之间的顺序需要特别小心,因为线程将独立执行,由于线程执行时间的运气,较早的数据块实际上可能会在较晚的数据块之后被处理。对于没有订购要求的加工,这不是问题。

        2、手动提交 offset 变得更加困难,因为它要求所有线程协调以确保该分区的处理完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/477041.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何构建高效的接口自动化测试框架?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在选择接口测试自动化框架时&#xff0c;需要根据团队的技术栈和项目需求来综合考虑。对于测试团队来说&#xff0c;使用Python相关的测试框架更为便捷。无论选…

数据结构-8.Java. 七大排序算法(上篇)

本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 上篇主要实现 前四种排序算法: 直接插入, 希尔, 选择, 堆排。 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 …

算法日记 32 day 动态规划(完全背包)

同样是背包问题&#xff0c;但01背包和完全背包是两个类型的问题。 完全背包&#xff1a; 完全背包与01背包的区别在于物品的个数是否是无限的。除此之外&#xff0c;在解决01背包的时候dp的背包遍历的顺利是倒序&#xff0c;为的是保证物品只被添加一次&#xff0c;而完全背包…

数据结构之树与二叉树

华子目录 1.树和二叉树的定义1.1树的定义1.2树的基本术语1.3线性结构和树结构1.4二叉树的定义 2.二叉树的性质和存储结构2.1二叉树的性质2.2二叉树的存储结构2.2.1顺序存储2.2.2链式存储 2.3遍历二叉树2.4大作业&#xff1a;二叉树的基本操作2.4.1代码思路&#xff08;仅供参考…

MYSQL——多表设计以及数据库中三种关系模型

大致介绍数据库中三种关系模型 一对多&#xff08;1:N&#xff09; 定义&#xff1a; 一个实体可以与另一个实体的多个实例相关联&#xff0c;而后者只能与前者的一个实例相关联。 例子&#xff1a; 学生和课程的关系。 学生&#xff08;1&#xff09;&#xff1a;每个学生…

企业网页设计的安全与数据保护

企业网页设计不仅要考虑美观和功能性&#xff0c;安全与数据保护也是重中之重。在这个信息爆炸的时代&#xff0c;用户的数据隐私和安全问题日益凸显&#xff0c;企业必须采取多种措施来保障用户的信息安全。 首先&#xff0c;**SSL加密**是基础中的基础。通过使用SSL证书&…

观察者模式和订阅模式

观察者模式和订阅模式在概念上是相似的&#xff0c;它们都涉及到一个对象&#xff08;通常称为“主题”或“发布者”&#xff09;和多个依赖对象&#xff08;称为“观察者”或“订阅者”&#xff09;之间的关系。然而&#xff0c;尽管它们有相似之处&#xff0c;但在某些方面也…

logback动态获取nacos配置

文章目录 前言一、整体思路二、使用bootstrap.yml三、增加环境变量四、pom文件五、logback-spring.xml更改总结 前言 主要是logback动态获取nacos的配置信息,结尾完整代码 项目springcloudnacosplumelog&#xff0c;使用的时候、特别是部署的时候&#xff0c;需要改环境&#…

工具学习_Docker

0. Docker 简介 Docker 是一个开源平台&#xff0c;旨在帮助开发者构建、运行和交付应用程序。它通过容器化技术将应用程序及其所有依赖项打包在一个标准化的单元&#xff08;即容器&#xff09;中&#xff0c;使得应用程序在任何环境中都能保持一致的运行效果。Docker 提供了…

基础知识学习上

基础知识学习上 1.关于print1.1 format 方法 2.运算符2.1 除法运算2.2 幂运算 3.条件控制语句3.1 if语句3.2 循环语句 4.复杂数据类型4.1列表4.2字典4.3字符串 5.函数 1.关于print 分隔符 print(1, 2, 3, 4, sep-) print(1, 2, 3, 4, sep。)结尾符 print(1, 2, 3, 4, end?) pr…

无监督跨域目标检测的语义一致性知识转移

Semantic consistency knowledge transfer for unsupervised cross domain object detection 无监督跨域目标检测的语义一致性知识转移 作者: Zichong Chen, Ziying Xia, Xiaochen Li, Junhao Shi, Nyima Tashi, Jian Cheng 所属机构: 电子科技大学信息与通信工程学院&…

AI智能稿件排版系统订单管理系统

在现代制造业和服务行业中&#xff0c;高效的生产流程和精确的订单管理是企业保持竞争优势的核心要素。AI智能稿件排版系统和订单管理系统作为一体化解决方案&#xff0c;以其强大的自动化能力和智能化技术&#xff0c;帮助企业实现排版效率提升、数据格式兼容性增强和生产流程…

Android Google登录接入

官方文献&#xff1a; 1、前期准备&#xff1a; https://developers.google.cn/identity/sign-in/android/legacy-start-integrating?hlzh-cnhttps://developers.google.cn/identity/sign-in/android/legacy-start-integrating?hlzh-cn 2、具体开发&#xff1a; 新版 Googl…

论文浅尝 | MindMap:知识图谱提示激发大型语言模型中的思维图(ACL2024)

笔记整理&#xff1a;和东顺&#xff0c;天津大学硕士&#xff0c;研究方向为软件缺陷分析 论文链接&#xff1a;https://aclanthology.org/2024.acl-long.558/ 发表会议&#xff1a;ACL 2024 1. 动机 虽然大语言模型&#xff08;LLMs&#xff09;已经在自然语言理解和生成任务…

Spring Cloud Data Flow快速入门Demo

1.什么是Spring Cloud Data Flow&#xff1f; Spring Cloud Data Flow 是一个用于构建和编排数据处理流水线的云原生框架。它提供了一种简化的方式来定义、部署和管理数据处理任务和流应用程序。以下是一些关键特性和组件&#xff1a; 关键特性 流处理&#xff1a; 支持实时数…

C# .NET环境下调用ONNX格式YOLOV8模型问题总结

我的环境是&#xff1a; Visual Studio: 2019 显卡&#xff1a; 一、遇到问题 1、EntryPointNotFoundException:无法在DLL“onnxruntime”中找到名为“OrtGetApiBase”的入口点。差了下原因&#xff0c;入口点是启动项中的问题。 原因&#xff1a;之前用yolov7时安装的版本在C…

量子感知机

神经网络类似于人类大脑&#xff0c;是模拟生物神经网络进行信息处理的一种数学模型。它能解决分类、回归等问题&#xff0c;是机器学习的重要组成部分。量子神经网络是将量子理论与神经网络相结合而产生的一种新型计算模式。1995年美国路易斯安那州立大学KAK教授首次提出了量子…

AI Large Language Model

AI 的 Large Language model LLM , 大语言模型&#xff1a; 是AI的模型&#xff0c;专门设计用来处理自然语言相关任务。它们通过深度学习和庞大的训练数据集&#xff0c;在理解和生成自然语言文本方面表现出色。常见的 LLM 包括 OpenAI 的 GPT 系列、Google 的 PaLM 和 Meta…

运维团队3D可视化智能机房管理方案

随着信息技术的飞速发展&#xff0c;机房作为信息技术基础设施的核心部分&#xff0c;其管理效率与可视化程度对运维团队的工作质量有着直接影响。本文将介绍一种结合3D可视化技术的机房管理方案&#xff0c;为运维团队提供一种新的视角和工具&#xff0c;以提升机房管理的效率…

CKA认证 | Day2 K8s内部监控与日志

第三章 Kubernetes监控与日志 1、查看集群资源状态 在 Kubernetes 集群中&#xff0c;查看集群资源状态和组件状态是非常重要的操作。以下是一些常用的命令和解释&#xff0c;帮助你更好地管理和监控 Kubernetes 集群。 1.1 查看master组件状态 Kubernetes 的 Master 组件包…