转载请注明出处:https://www.cnblogs.com/zjdxr-up/p/16114877.html

  目录:

   3.1 消费者与消费组

    3.2 消息消费过程及代码

    3.3 消息消费模式

    3.4 位移提交

    3.5 位移提交过程导致重复消费的现象

    3.6 再均衡

    3.7Kafka消费端重要的参数    

3.1 消费者与消费组

  消费者(Consumer)负责订阅Kafka 中的主题(Topic), 并且从订阅的主题上拉取消息。与其他一些消息中间件不同的是: 在Kafka 的消费理念中还有一层消费组(Consumer Group)的概念, 每个消费者都有一个对应的消费组。 当消息发布到主题后, 只会被投递给订阅它的每个消费组中的一个消费者。

  每一个分区只能被一个消费组中的一个消费者所消费。

  对于消息中间件而言,一般有两种消息投递模式:点对点(P2P, Point-to-Point)模式和发布/订阅(Pub/ Sub)模式。点对点模式是基于队列的,消息生产者发送消息到队列,消息消费者从队列中接收消息。发布订阅模式定义了如何向 一个内容节点发布和订阅消息,这个内容节点称为主题(Topic) , 主题可以认为是消息传递的中介,消息发布者将消息发布到某个主题,而消息订阅者从主题中订阅消息。主题使得消息的订阅者和发布者互相保持独立,不需要进行接触即可保证消息的传递,发布/订阅模式在消息的一对多广播时采用。Kafka 同时支待两种消息投递模式,而这正是得益于消费者与消费组模型的契合:

     • 如果所有的消费者都隶属于同一个消费组,那么所有的消息都会被均衡地投递给每一个消费者,即每条消息只会被一个消费者处理,这就相当于点对点模式的应用。

     • 如果所有的消费者都隶属于不同的消费组,那么所有的消息都会被广播给所有的消费者,即每条消息会被所有的消费者处理,这就相当于发布/订阅模式的应用。

  消费组是一个逻辑上的概念,它将旗下的消费者归为 一类,每一个消费者只隶属于一个消费组。每一个消费组都会有一个固定的名称,消费者在进行消费前需要指定其所属消费组的名称,这个可以通过消费者客户端参数group.id来配置,默认值为空字符串。

3.2 消息消费过程及代码

  一个正常的消费逻辑需要具备以下几个步骤:

    (1) 配置消费者客户端参数及创建相应的消费者实例。

    (2) 订阅主题。

    (3)拉取消息并消费。

    (4) 提交消费位移。

    (5)关闭消费者实例。

public class KafkaConsumerAnalysis {
public static final String brokerList = "localhost:9092";
public static final String topic = "topic-demo";
public static final String groupid = "group.demo";
public static final AtomicBoolean isRunning = new AtomicBoolean(true);
public static Properties initConfig () {
Properties props= new Properties();
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS—CONFIG,StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokerList);
props.put(ConsumerConfig.GROUP—ID_CONFIG, groupid);
props. put (ConsumerConfig. CLIENT_ID _ CONFIG, "client. id. demo");
return props;
} public static void main(String[] args) (
Properties props= initConfig();
KafkaConsumer<String, String> consumer= new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList(topic));
try {
while (isRunning. get()) {
ConsumerRecords<String, String> records=
consumer.poll(Duration.ofMillis(lOOO));
for (ConsumerRecord<String, String> record : records) {
System.out.println("topic="+record.topic()+ ", partition = "+ record.partition()+ ", offset="+ record.offset());
System.out.println("key ="+record.key()+ ", value="+ record.value());
//do something to process record.
} catch(Exception e) {
log.error("occur exception", e);
} finally {
consumer.close();
}
}
}

    通过 subscribe()方法订阅主题具有 消费者自动再均衡的功能,在多个消费者的情况下可以根据分区分配策略来自动分配各个消费者与分区的关系。当消费组内的消费者增加或减少时,分区分配关系会自动调整,以实现消费负载均衡及故障自动转移。

    如果我们事先并不知道主题中有多少个分区怎么办?KafkaConsumer中的partitionsFor ()方法可以用来查询指定主题的元数据信息,partitionsFor()方法的具体定义如下:

public List<Partitioninfo> partitionsFor(String topic)

    其中 Partitionlnfo类型即为主题的分区元数据信息,此类的主要结构如下:

public class Partitioninfo {
private final String topic;
private final int partition;
private final Node leader;
private final Node[] replicas;
private final Node[] inSyncReplicas;
private final Node[] offlineReplicas;
//这里省略了构造函数、属性提取、toString等方法
}

    Partitioninfo类中的属性topic表示主题名称,partition代表分区编号,leader代表分区的leader副本所在的位置,replicas代表分区的AR集合,inSyncReplicas代表分区的ISR集合,offlineReplicas代表分区的OSR集合。

3.3 消息消费模式

    Kafka中的消费是基于 拉模式的。消息的消费一般有两种模式:推模式和 拉模式。推模式是服务端主动将消息推送给消费者, 而 拉模式是消费者主动向服务端发起请求来拉取消息。Kafka中的消息消费是一个不断轮询的过程,消费者所要做的就是重复地调用poll()方法,而poll()方法返回的是所订阅的主题(分区)上的一组消息。 对于poll()方法而言,如果某些分区中没有可供消费的消息,那么此分区对应 的消息拉取的结果就为空;如果订阅的所有分区中都没有可供消费的消息, 那么poll()方法返回为空的消息集合

    消费者消费到 的每条消息的类型为ConsumerRecord(注意与ConsumerRecords 的区别,ConsumerRecords为一次获取到的消息集),这个和生产者发送的消息类型ProducerRecord相对应,不过ConsumerRecord中的内容更加丰富,具体的结构参考如下代码:

public class ConsumerRecord<K, V> {
private final Stringtopic;
private final int partition;
private final long offset;
private final long timestamp;
private final TimestampType timestampType;
private final int serializedKeySize;
private final int serializedValueSize;
private final Headers headers;
private final K key;
private final V value;
private volatile Long checksum;
//省略若干方法

    timestarnpType 有两种类型:CreateTime 和LogAppendTime, 分别代表消息创建的时间戳和消息追加到日志的时间戳。

3.4 位移提交

    对于 Kafka 中的分区而言,它的每条消息都有唯一 的 offset,用来表示消息在分区中对应 的位置 。 对于消费者而言 , 它也有一个 offset 的概念,消费者使用 offset 来表示消费到分区中某个消息所在的位置。

    在每次调用 poll ()方法时,它返回的是还没有被消费过的消息集(当然这个前提是消息己经存储在 Kafka 中 了,并且暂不考虑异常情况的发生),要做到这一点,就需要记录上一 次消费时的消费位移 。 并且这个消费位移必须做持久化保存,而不是单单保存在内存中,否则消费者重启之后就无法知晓之前的消费位移 。

    在旧消费者客户端中,消费位移是存储在 ZooKeeper 中的 。 而在新消费者客户端中,消费位移存储在 Kafka 内 部的主题consumer offsets 中 。 这里把将消费位移存储起来(持久化)的动作称为“提交’,消费者在消费完消息之后需要执行消费位移的提交。

    在Kafka 中默认的消费位移的提交方式是自动提交,这个由消费者客户端参数enable. auto. commit 配置,默认值为 true。当然这个默认的自动提交不是每消费一条消息就提交一次,而是定期提交,这个定期的周期时间由客户端参数 auto. commit. interval. ms配置,默认值为 5 秒,此参数生效的前提是 enable. auto.commit 参数为 true 。

    在默认的方式下,消费者每隔 5 秒会将拉取到的每个分区中最大的消息位移进行提交 。自动位移提交的动作是在 poll()方法的逻辑里完成的,在每次真正向服务端发起拉取请求之前会检查是否可以进行位移提交,如果可以,那么就会提交上一次轮询的位移。

3.5 位移提交过程导致重复消费的现象

    如果在业务逻辑处理完之后,并且在同步位移提交前,程序出现了崩渍 ,那么待恢复之后又只能从上一次位移提交的地方拉取消息,由此在两次位移提交的窗口中出现了重复消费的现象。

    KafkaConsumer 中的 seek()方法提供了追前消费或 回溯消费。

public void seek(TopicPartition partition ,long offset)

    seek()方法中的参数 partition 表示分区,而 offset 参数用来指定从分区的哪个位置开始消费。seek()方法只能重置消费者分配到的分区的消费位置,而分区的分配是在poll()方法的调用过程中实现的 。 也就是说,在执行 seek()方法之前需要先执行一次 poll ()方法 ,等到分配到分区之后才可以重置消费位置 。

KafkaConsumer <String ,String> consumer= new KafkaConsumer<> (props);
consumer.subscribe(Arrays.asList(topic));
consumer . poll(Duration.ofMillis(lOOOO));
Set<TopicPartition> assignment = consumer.assignment();
for(TopicPartition tp : assignment) {
consumer.seek(tp , 10) ;
while(true) {
ConsumerRecords<String , String> records = consumer.poll(Duration.ofMillis (1000)) ;
//consume the record .
}

    timeout参数用来设置等待获取的超时时间。如果没有指 定timeout参数的值, 那么endOffsets() 方 法 的 等 待时 间由客户端参 数request.timeout.ms来设置,默认值为30000。

    seek()方法为我们提供了从特定位置读取消息的能力,我们可以通过这个方法来向前跳过若干消息, 也可以通过这个方法来向后回溯若干消息, 这样为消息的消费提供了很大的灵活性。seek()方法也为我们提供了将消费位移保存在外部存储介质中的能力,还可以配合再均衡监听器来提供更加精准的消费能力。

3.6 再均衡

    再均衡是指分区的所属权从一个消费者转移到另一消费者的行为, 它为消费组具备高可用性和伸缩性提供保障, 使我们可以既方便又安全地删除消费组内的消费者或往消费组内添加消费者。 不过在再均衡发生期间, 消费组内的消费者是无法读取消息的。 也就是说, 在再均衡发生期间的这一小段时间内, 消费组会变得不可用。 另外, 当 一个分区被重新分配给另 一个消费者时, 消费者当前的状态也会丢失。 比如消费者消费完某个分区中的一部分消息时还没有来得及提交消费位移就发生了再均衡操作, 之后这个分区又被分配给了消费组内的另 一个消费者,原来被消费完的那部分消息又被重新消费 一遍, 也就是发生了重复消费。 一般情况下, 应尽量避免不必要的再均衡的发生。

    subscribe()方法中有再均衡监听器ConsumerRebalanceListener, 在subscribe(Collection<String> topics, ConsumerRebalanceListener listener)和subscribe(Pattem pattern, ConsumerRebalanceListener listener)方法中都有它的身影。再均衡监听器用来设定发生再均衡动作前后的一些准备或收尾的动作。 ConsumerRebalanceListener是 一个接口.

3.7Kafka消费端重要的参数

参数名称 默认值 参数释义
bootstrap.servers “” 指定连接 Kafka 集群所需的 broker 地址清单
key.deserializer   消息key对应的反序列化类,需要实现org.apache.kafka.common.serialization.Deserializer接口
value.deserializer   消息key 所对应的反序列化类,需要实现org.apache.kafka.common.serialization.Deserializer接口
group.id "" 消费者所隶属的消费组的唯一标识,即消费组的名称
session. timeout.ms 10000 组管理协议中用来检测消费者是否失效的超时时间
max.poll.interval.ms 300000 消费组管理消费者时,该配置指定拉取消息线程最长空闲时间,若超过这个时 间间 隔还没有发起 poll 操作,则消费组认为该消费者己离开了消费组 ,将进行再均衡操作
auto.offset.reset latest 有效值为“ earliest ”" latest ” “ none”
enable.auto.commit true 是否开启自动提交消费位移的功能,默认开启
auto.commit.interval.ms 5000 当 enable.auto.commit 参数设置为 true 时才生效 ,表示开启自动提交消费位移功能 时自 动提交消费位移的时间间 隔
partition.assignment. strategy   消费者的分区分配策略
fetch .min.bytes 1( B ) Consumer 在一次拉取中从 Kafka 中拉取的最小数据量
fetch .max.bytes 50MB Consumer 在一次拉取中从 Kafka 中拉取的最大数据量
max.poll.records 500条 Consumer 在一次拉取请求中拉取的最大消息数
connections.max.idle.ms 9分钟 用来指定在多久之后关闭限制的连接
isolation.level read_ uncommitted 事务隔离级别。字符串类型,有效值为“ read_uncommitted ,和“ read committed ",表示消费者所消费到的位置,可以消费到 HW (High Watermark )处的位置
  

深入理解Kafka核心设计及原理(一):初始Kafka

深入理解Kafka核心设计及原理(二):生产者

深入理解Kafka核心设计及原理(三):消费者的更多相关文章

  1. 深入理解Kafka核心设计及原理(四):主题管理

    转载请注明出处:https://www.cnblogs.com/zjdxr-up/p/16124354.html 目录: 4.1创建主题 4.2 优先副本的选举 4.3 分区重分配 4.4 如何选择合 ...

  2. 深入理解Kafka核心设计及原理(五):消息存储

    转载请注明出处:https://www.cnblogs.com/zjdxr-up/p/16127749.html 目录: 5.1文件目录布局 5.2消息压缩 5.3日志索引 5.4日志文件及索引文件分 ...

  3. 深入理解Kafka核心设计及原理(二):生产者

    转载请注明出处: 2.1Kafka生产者客户端架构 2.2 Kafka 进行消息生产发送代码示例及ProducerRecord对象 kafka进行消息生产发送代码示例: public class Ka ...

  4. 深入理解kafka设计原理

    最近开研究kafka,下面分享一下kafka的设计原理.kafka的设计初衷是希望作为一个统一的信息收集平台,能够实时的收集反馈信息,并需要能够支撑较大的数据量,且具备良好的容错能力. 1.持久性 k ...

  5. Kafka 设计与原理详解

    一.Kafka简介 本文综合了我之前写的kafka相关文章,可作为一个全面了解学习kafka的培训学习资料. 转载请注明出处 : 本文链接 1.1 背景历史 当今社会各种应用系统诸如商业.社交.搜索. ...

  6. 从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(转载)

    从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理 1.前言   在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或经纬度)的换算方法, ...

  7. 最全Kafka 设计与原理详解【2017.9全新】

    一.Kafka简介 1.1 背景历史 当今社会各种应用系统诸如商业.社交.搜索.浏览等像信息工厂一样不断的生产出各种信息,在大数据时代,我们面临如下几个挑战: 如何收集这些巨大的信息 如何分析它 如何 ...

  8. Kafka设计解析(三)Kafka High Availability (下)

    转载自 技术世界,原文链接 Kafka设计解析(三)- Kafka High Availability (下) 摘要 本文在上篇文章基础上,更加深入讲解了Kafka的HA机制,主要阐述了HA相关各种场 ...

  9. [转]Kafka 设计与原理详解

    一.Kafka简介 本文综合了我之前写的kafka相关文章,可作为一个全面了解学习kafka的培训学习资料. 1 2 1 2 转载请注明出处 : 本文链接 1.1 背景历史 当今社会各种应用系统诸如商 ...

随机推荐

  1. 9.Flink实时项目之订单宽表

    1.需求分析 订单是统计分析的重要的对象,围绕订单有很多的维度统计需求,比如用户.地区.商品.品类.品牌等等.为了之后统计计算更加方便,减少大表之间的关联,所以在实时计算过程中将围绕订单的相关数据整合 ...

  2. java线程池之newFixedThreadPool定长线程池

    newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待. 线程池的作用: 线程池作用就是限制系统中执行线程的数量.     根 据系统的环境情况,可以 ...

  3. J20航模遥控器开源项目系列教程(五)| 制作STM32F0接收机,8路PWM输出,SBUS输出,PPM输出 | 加密狗无线化,畅玩飞行模拟器

    我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/J ...

  4. 配置Django环境后,运行时报错

    (背景)安装完Django,并配置完成. 在setting.py中设置了数据库时,出现的报错. 点击查看 数据库配置 DATABASES = { 'default': { # 'ENGINE': 'd ...

  5. KMP 算法中的 next 数组

    KMP 算法中对 next 数组的理解 next 数组的意义 此处 next[j] = k:则有 k 前面的浅蓝色区域和 j 前面的浅蓝色区域相同: next[j] 表示当位置 j 的字符串与主串不匹 ...

  6. 文字图片在wps中清晰化方法

    在wps中双击图片出属性,然后再选择文字增强.选择对比增加即可.

  7. 迷宫问题,打印所有路径,深度搜索,dfs

    #include<iostream> using namespace std; int maze [5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0 ...

  8. Java 中计算注意!!!

    * 使用BigDecimal需要注意的事项:  * 1.两个BigDecimal值不能使用" +, -, *, / " 进行加减乘除,要使用" add, substrac ...

  9. win10 doskey宏命令定义,类似于Linux的alias别名命令

    doskey 命令别名=命令 例如:doskey echo2 = echo $1 这里的$1是占位符. 如果想删除,直接赋予空值即可:例如:doskey echo2= 总的来说把 https://do ...

  10. 怎么将 byte 转换为 String?

    可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用 的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也 可能不同.