Kafka的消息模型为发布订阅模型,消息生产者将消息发布到主题(topic)中,一个或多个消费者订阅(消费)该主题消息并消费,此模型中发布到topic中的消息会被所有消费者所订阅到,先介绍Kafka消费模型,然后再通过KafkaConsumer原来了解它的业务流程,源码基于kafka 2.4;

Kafka消费模型关键点:

  1、Kafka一个消费组(ConsumerGroup)中存在一个或多个消费者(Consumer),每个消费者也必须属于一个消费者组;

  2、消费者组(ConsumerGroup)中的消费者(Consumer)独占一个或多个分区(Partition);

  3、消费时每个分区(Partition)最多只有一个Consumer再消费;

  4、消费者组(ConsumerGroup)在Broker存在一个协调者(Coordinator)分配管理Consumer与Partition之间的对应关系。当两种中的Consumer或Partition发生变更时将会触发reblance(重新平衡),重新分配Consumer与Partition的对应关系;

下面是Kafka消费者程序的示例:

  1. //配置Consumer
  2. Properties props = new Properties();
  3. props.put("bootstrap.servers", "localhost:9092");
  4. props.put("group.id", "test");
  5. props.put("enable.auto.commit", "true");
  6. props.put("auto.commit.interval.ms", "1000");
  7. props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
  8. props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
  9. //创建Consumer
  10. KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
  11. //订阅主题
  12. consumer.subscribe(Arrays.asList("foo", "bar"));
  13. //消费消息
  14. while (true) {
  15. ConsumerRecords<String, String> records =
  16. consumer.poll(100);
  17. for (ConsumerRecord<String, String> record : records)
  18. System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
  19. }

  在上面我们可以看到Kafka消费消息的整个流程:配置Consumer属性、订阅主题、拉取消费消息,基本流程知道了也就是这几个点,配置ConsumerId、自动提交offset、序列化、Kafka服务端地址,这就是Kafka最最最基础的配置,当然还有很多配置项可以到官网查看;

消费者关键点

  Consumer程序主要分为三个部分:配置、订阅主题、拉取消息;从中也可以看到在消费前需要订阅某个主题、在前面我们提到Consumer实例需要与某个Partition绑定关联然后才能进行消费数据,下面我们透过官方提供的Consumer程序简单看看如何订阅主题、如何关联Consumer与Partition、如何拉取消息消费;

订阅主题

  订阅主题可以说是Kafka消费的基础,下面先看看简化后的订阅方法:

  1. public void subscribe(Collection<String> topics, ConsumerRebalanceListener listener) {
  2. acquireAndEnsureOpen();
  3. try {
  4. //忽略部分代码
  5. if (topics.isEmpty()) {
  6. this.unsubscribe();
  7. } else {
  8. if (this.subscriptions.subscribe(new HashSet<>(topics), listener))
  9. metadata.requestUpdateForNewTopics();
  10. }
  11. } finally {
  12. release();
  13. }
  14. }

  安全检查: Consumer注释中也说了KafkaConsumer为非线程安全的,从上也可看到acquireAndEnsureOpen的作用就是检查当前是否为多线程运行,确保Consumer只在一个线程中执行;

  设置订阅状态: SubscriptionState 对象的subscribe方法主要是设置ConsumerRelance监听器、设置所监听的主题;

  更新元数据: metadata对象维护了Kafka集群元数据子集,存储了Broker节点、Topic、Partition节点信息等;

  跟进metadata.requestUpdateForNewTopics方法发现最终调用了metadata对象的requestUpdate方法;

  1. public synchronized int requestUpdate() {
  2. this.needUpdate = true;
  3. return this.updateVersion;
  4. }

  此方法并没有什么实质性的动作,只是更新needUpdate属性为true;由于Kafka拉取数据时必须得到元数据信息否则无法知道broker、topic、Partition信息也就无法知道去哪个节点拉取数据;但此处并没有实质性的更新元数据请求,接下来我们看看拉取方法。

拉取数据

  上一步订阅了主题,这时我们就可以从中拉取数据,跟踪代码最终进入了KafkaConsumer的poll方法;

  1. private ConsumerRecords<K, V> poll(final Timer timer, final boolean includeMetadataInTimeout) {
  2. //多线程检查
  3. acquireAndEnsureOpen();
  4. try {//省略代码
  5. //超时检查
  6. if (includeMetadataInTimeout) {
  7. //请求更新元数据
  8. if (!updateAssignmentMetadataIfNeeded(timer)) {
  9. return ConsumerRecords.empty();
  10. }
  11. } else {//省略代码
  12. }
  13. //拉取数据
  14. final Map<TopicPartition, List<ConsumerRecord<K, V>>> records = pollForFetches(timer);
  15. if (!records.isEmpty()) {
  16. if (fetcher.sendFetches() > 0 || client.hasPendingRequests()) {
  17. client.pollNoWakeup();
  18. }
  19. //调用消费者拦截器后返回
  20. return this.interceptors.onConsume(new ConsumerRecords<>(records));
  21. }
  22. return ConsumerRecords.empty();
  23. } finally {
  24. release();
  25. this.kafkaConsumerMetrics.recordPollEnd(timer.currentTimeMs());
  26. }
  27. }

此方法几个流程

1、 多线程检查

2、 超时检查

3、 请求更新元数据

4、 拉取数据

  此处我们比较关心的还是更新元数据与拉取数据,这里我们主要看看这两个流程的执行;

请求更新元数据

  在updateAssignmentMetadataIfNeeded方法中调用coordinator对象的poll方法去更新元数据,并且调用updateFetchPositions方法用于刷新Consumer对应Partition对应的offset值;

拉取数据

  数据的拉取在pollForFetches方法中;

  1. private Map<TopicPartition, List<ConsumerRecord<K, V>>> pollForFetches(Timer timer) {
  2. //省略代码
  3. //从缓存区数据
  4. final Map<TopicPartition, List<ConsumerRecord<K, V>>> records = fetcher.fetchedRecords();
  5. if (!records.isEmpty()) {
  6. return records;
  7. }
  8. //构造拉取请求发送
  9. fetcher.sendFetches();
  10. //省略代码
  11. //发起拉取数据请求
  12. Timer pollTimer = time.timer(pollTimeout);
  13. client.poll(pollTimer, () -> {
  14. // since a fetch might be completed by the background thread, we need this poll condition
  15. // to ensure that we do not block unnecessarily in poll()
  16. return !fetcher.hasAvailableFetches();
  17. });
  18. timer.update(pollTimer.currentTimeMs());
  19. //省略代码
  20. return fetcher.fetchedRecords();

}

pollForFetches方法执行逻辑:

  1、 从缓存取数据如有可用数据,直接返回;

  2、 构造请求对象fetches,一个节点node对应一个clientRequest对象,将其放入ConsumerNetworkClient对象的unsent属性中;

  3、 调用client对象poll方法,将上一步放入unsent属性的请求对象ClientRequest发送出去;

  4、 返回所拉取到的消息;

Offset提交

  offset提交放在ConsumerCoordinator对象中,offset提交又分为自动提交与手动提交;当设置了enable.auto.commit==true且  autoCommitIntervalMs等于指定间隔时有这么几个时机会触发自动:

  1、 consumer对象close时,调用commitOffsetsSync触发同步的offset提交;

  2、 consumer对象poll时,调用commitOffsetsAsync触发异步的offset提交;

  3、 触发Partition与Topic 分配 assign时触发commitOffsetsAsync异步提交;

  4、 当发生relance或有Consumer加入Group时触发commitOffsetsSync方法同步提交;

参考资料: http://kafka.apache.org

从KafkaConsumer看看Kafka(一)的更多相关文章

  1. Windbg调优Kafka.Client内存泄露

    从来没写过Blog,想想也是,工作十多年了,搞过N多的架构.技术,不与大家分享实在是可惜了.另外,从传统地ERP行业转到互联网,也遇到了很所前所未有的问题,原来知道有一些坑,但是不知道坑太多太深.借着 ...

  2. Python 使用python-kafka类库开发kafka生产者&消费者&客户端

    使用python-kafka类库开发kafka生产者&消费者&客户端   By: 授客 QQ:1033553122       1.测试环境 python 3.4 zookeeper- ...

  3. Kafka消费者组再均衡问题

    在Kafka中,当有新消费者加入或者订阅的topic数发生变化时,会触发Rebalance(再均衡:在同一个消费者组当中,分区的所有权从一个消费者转移到另外一个消费者)机制,Rebalance顾名思义 ...

  4. KafkaConsumer 长时间地在poll(long )方法中阻塞

    一,问题描述 搭建的用来测试的单节点Kafka集群(Zookeeper和Kafka Broker都在同一台Ubuntu上),在命令行下使用: ./bin/kafka-topics. --replica ...

  5. python连接kafka生产者,消费者脚本

    # -*- coding: utf-8 -*- ''''' 使用kafka-Python 1.3.3模块 # pip install kafka==1.3.5 # pip install kafka- ...

  6. kafka consumer重复消费问题

    在做分布式编译的时候,每一个worker都有一个consumer,适用的kafka+zookeep的配置都是默认的配置,在消息比较少的情况下,每一个consumer都能均匀得到互不相同的消息,但是当消 ...

  7. kafka+docker+python

    昨天晚上刚刚才花3小时看完<日志:每个软件工程师都应该知道的有关实时数据的统一概念>. 今天就把kafka在docker容器里运行起来,github上有几个,但都太复杂了. 我自己写个最简 ...

  8. Kafka 温故(五):Kafka的消费编程模型

    Kafka的消费模型分为两种: 1.分区消费模型 2.分组消费模型 一.分区消费模型 二.分组消费模型 Producer : package cn.outofmemory.kafka; import ...

  9. kafka offset 设置

    from kafka import KafkaConsumer from kafka import TopicPartition from kafka.structs import OffsetAnd ...

随机推荐

  1. Java常用类、接口关系图谱

    呕心沥血画出此图,希望在使用Java类.接口时捋顺其关系,从而更好的组织程序逻辑---请看图 Object分出来的类都是其子类 Iterable接口分出的也是子接口 从继承关系分析,其父类实现的接口子 ...

  2. iOS开发调试概览

    概述 我们都知道Xcode默认的调试器是LLDB(在此之前使用的是GDB),但是关于LLDB的debug技巧并非所有人都比较清楚,可能所有人都知道p或者po命令打印一些变量.但是实际的情况时这些还远远 ...

  3. 【RN - 基础】之React Native常见问题及解决方案

    unable to load script from assets index.android.bundle... 问题原因: 找不到Android项目中的assets文件夹. 解决方案: 1.在An ...

  4. 【JavaEE】之MyBatis插入数据后获取自增主键

    很多时候,我们都需要在插入一条数据后回过头来获取到这条数据在数据表中的自增主键,便于后续操作.针对这个问题,有两种解决方案: 先插入,后查询.我们可以先插入一条数据,然后根据插入的数据的各个字段值,再 ...

  5. warning: rpmts_HdrFromFdno: Header V4 DSA/SHA1 Signature, key ID XXXXXX: NOKEY

    我在使用Centos时,会出现这种错误: 本人实践有效的办法是: 加上"--nogpgcheck"参数 就是在你要执行的语句后面加上该参数就行了! 我当时是为了安装jenkins时 ...

  6. OOXML中回车等特殊字符处理方法

    问题点:NPOI处理xlsx文档时,将\r写成了换行符. 实例:以下字符abc\rcde 如果直接复制到Excel 2016,显示结果如下(单元格设置为折行显示): 如果用NPOI写入Xlsx文档,显 ...

  7. Chapter 04—Basic Data Management

    1. 创建新的变量 variable<-expression expression:包含一组大量的操作符和函数.常用的算术操作符如下表: 例1:根据已知变量,创建新变量的三种途径 > my ...

  8. c++关于multiset的头文件包含问题

    最近在Bilibili上看到不少侯捷老师C++的视频教程,侯捷老师翻译了很多C++的经典书籍,比如<Essential C++中文版>.<STL源码剖析>,也写了<深入浅 ...

  9. ThinkPHP5入门(基础篇)

    ThinkPHP是一个快速.简单的基于MVC和面向对象的轻量级PHP开发框架,自2006年诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简代码的同时,尤其注重开发体验和易用性,并且拥有众多的 ...

  10. Numpy用于数组数据的存储和读取

    Python的Numpy模块可用于存储和读取数据: 1.将一个数组存储为二进制文件 Numpy.save:将一个数组以.npy的格式保存为二进制文件 调用格式:numpy.save(file, arr ...