kafka--消费者详解

一、消费者和消费者组

1.简介

与生产者对应的是消费者, 应用程序可以通过KafkaConsumer来订阅主题, 并从订阅的topic中拉取消息。 与其他 些消息中间件不同的是: 在Kaflca 的消费理念中还有 层消费组(Consumer Group) 的概念, 每个消费者都有一 个对应的消费组。 当消息发布到主题后, 只会被投递给订阅它的每个消费组中的一 个消费者
如图所示, 某个主题中共有4个分区(Part山on): PO Pl P2 P3。 有两个消费组A和B都订阅了这个主题, 消费组A中有4个消费者(CO、 Cl、 C2 和C3), 消费组B中有2 个消费者(C4和C5)。 按照Kafka默认的规则, 最后的分配结果是消费组A中的每一 个消费
者分配到1个分区, 消费组B中的每 个消费者分配到 2 个分区, 两个消费组之间互不影响。 每个消费者只能消费所分配到的分区中的消息。 换言之, 每一 个分区只能被 个消费组中的 一 个消费者所消费。所以一个消费者组中的消费者数量不能超过分区数,超过部分的消费者将会一直空闲

2.应用场景:

(1)如果生产者写入消息的速度比消费者读取的速度快怎么办呢?这样随着时间增长,消息堆积越来越严重。对于这种场景,我们需要增加多个消费者来进行水平扩展。

(2)如果一个应用想要获取所有消息那么就可以将整个应用配置成一个消费者组,如果需要分环境,比如线上和预发都需要消费一遍那么就需要将线上和预发配置成两个消费者组

3.消息投递模式

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

4.工作流程:

  1. 连接 ZK 集群,拿到对应 topic 的 partition 信息和 partition 的 leader 的相关信息
  2. 连接到对应 leader 对应的 broker
  3. consumer 将自己保存的 offset 发送给 leader
  4. leader 根据 offset 等信息定位到 segment(索引文件和日志文件)
  5. 根据索引文件中的内容,定位到日志文件中该偏移量对应的开始位置读取相应长度的数据并返回给 consumer

二、订阅主题(topic)与分区(partition)

下一章会详解主题和分区

在创建好消费者之后, 我们就需要为该消费者订阅相关的主题了。 一个消费者可以订阅一个或多个主题,只需要通过调用subscribe()方法即可,这个方法接收一个主题列表
nsumer.subscribe(Collections.singletonList("topic"));

也可以通过正则的方式订阅

consumer.subscribe(Pattern.compile("topic-.*"));

如果想直接订阅某个主题下的分区就可以使用assign()方法,如下是订阅了topic-demo主题 中分区编号为0的分区

consumer.assign(Arrays.asList(new TopicPartition("topic-demo", 0)));
既然有订阅,那么就有取消订阅
consumer.unsubscribe();

还有其他的一些订阅和取消订阅的方式就略过了、

三、反序列化

在生产者一章中提到过序列化,反序列化顾名思义就是和序列化对应的,将字节数组转换回原来的消息,

Kaflca所提供的反序列化器有 ByteBufferDeserializer、ByteArrayDeserializer、 BytesDeserializer、 DoubleDeserializer、 FloatDeserializer、 IntegerDeserializer、 LongDeserializer、 ShortDeserializer、StringDeserializer, 它们分别用千ByteBuffer、ByteArray、Bytes、Double、Float、 Integer、Long、 Short 及 String类型的反序列化,这些序列化器也都实现了Deserializer接口,

四、消息消费

1.poll()方法

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

2. offset偏移量(位移)

Kafka 的每一条消息都有一个偏移量属性,记录了其在分区中的位置,偏移量是一个单调递增的整数。消费者通过往一个叫作 _consumer_offset 的特殊主题发送消息,消息里包含每个分区的偏移量。如果Kafka 消费者查找不到所记录的消费位移或者位移越界时, 就会根据消费者客户端参数 auto offset.reset 的配置来决定从何处开始进行消费,这个参数的默认值为“latest ”,表 示从分区末尾开始消费消息。

如果消费者一直处于运行状态,那么偏移量就没有什么用处。不过,如果有消费者退出或者新分区加入,此时就会触发再均衡。完成再均衡之后,每个消费者可能分配到新的分区,而不是之前处理的那个。为了能够继续之前的工作,消费者需要读取每个分区最后一次提交的偏移量,然后从偏移量指定的地方继续处理。 因为这个原因,所以如果不能正确提交偏移量,就可能会导致数据丢失或者重复出现消费,比如下面情况:

(1)如果提交的偏移量小于客户端处理的最后一个消息的偏移量 ,那么处于两个偏移量之间的消息就会被重复消费;

(2)如果提交的偏移量大于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失。

3.偏移量的提交

我们把将消费位移存储起来(持久化)的 动作称为“提交’ ,消费者在消费完消息之后需要执行消费位移的提交。提交分为两种:

(1)自动提交

kafka默认的就是自动提交,

实现方式很简单只需要将消费者的 enable.auto.commit 属性配置为 true 即可完成自动提交的配置。 此时每隔固定的时间,消费者就会把 poll() 方法接收到的最大偏移量进行提交,提交间隔由 auto.commit.interval.ms 属性进行配置,默认值是 5s。

使用自动提交是存在隐患的,假设我们使用默认的 5s 提交时间间隔,在最近一次提交之后的 3s 发生了再均衡,再均衡之后,消费者从最后一次提交的偏移量位置开始读取消息。这个时候偏移量已经落后了 3s ,所以在这 3s 内到达的消息会被重复处理。可以通过修改提交时间间隔来更频繁地提交偏移量,减小可能出现重复消息的时间窗,不过这种情况是无法完全避免的,或者如果在未提交偏移量时消费者服务器挂了也会造成重复消费的问题。基于这个原因,Kafka 也提供了手动提交偏移量的 API,使得用户可以更为灵活的提交偏移量。

自动提交会漏消费造成丢失数据的情况,如果想避免可以设置为手动提交

(2)手动提交偏移量

用户可以通过将 enable.auto.commit 设为 false,然后手动提交偏移量。基于用户需求手动提交偏移量可以分为两大类:

手动提交当前偏移量:即手动提交当前轮询的最大偏移量;
手动提交固定偏移量:即按照业务需求,提交某一个固定的偏移量。
而按照 Kafka API,手动提交偏移量又可以分为同步提交和异步提交。
 

同步提交:使用consumer.commitSync()提交,如果某个提交失败,同步提交还会进行重试,这可以保证数据能够最大限度提交成功,但是同时也会降低程序的吞吐量

异步提交:使用consumer.commitAsync()异步提交可以提高程序的吞吐量,异步提交存在的问题是,在提交失败的时候不会进行自动重试,实际上也不能进行自动重试。假设程序同时提交了 200 和 300 的偏移量,此时 200 的偏移量失败的,但是紧随其后的 300 的偏移量成功了,此时如果重试就会存在 200 覆盖 300 偏移量的可能。同步提交就不存在这个问题,因为在同步提交的情况下,300 的提交请求必须等待服务器返回 200 提交请求的成功反馈后才会发出。基于这个原因,某些情况下,需要同时组合同步和异步两种提交方式。

同步和异步组合提交:

如果一切正常,使用commitAsync()方法来提交。这样速度更快,而且即使这次提交失败,下一次提交很可能会成功。如果直接关闭消费者,就没有所谓的下一次提交了。在异常处理后使用commitSync()提交。

提交特定的偏移量:

上述几种方式的提交偏移量的频率与处理消息批次的频率是一样的,如果poll()方法返回一大批数据,为了避免因再均衡引起的重复处理整批消息,可以在调用commitAsync()和commitSync()方法时传进去希望提交的分区和偏移量的map

具体实现代码参考https://blog.csdn.net/m0_37809146/article/details/91126212

五、指定位移消费seek()

到目前为止,我们知道消息的拉取是根据 poll ()方法中的逻辑来处理的,这个 poll () 方法中 的逻辑对于普通的开发人员而言是 一个黑盒 ,无 法精确地掌控其消费 的起始 位置。提供 auto. offs et.reset  参数 只能在找不到消费位移或位移越界的情况 下粗粒 度地从开头或末尾开始消费。 有些时候,我们 需要一种更细粒度的掌控,可以让我 们从 特定的位移处开始拉 取消息,而 Kafka Consumer 中的 seek () 方法正好提供了这个功能,让我 以追前 消费或 回溯消费。
public void seek(TopicPartition partition,long offset)
seek () 方法中的参数 partiti on 表示分区,而 offset 参数用来指定从分区的哪个位置 开始消费。 seek ()方法只能重置消费者分配到的分区的消费位置,而分区的分配是在 po ll () 方 法的调用过程中实现的也就是说,在执行 seek ()方 法之前需要 先执行一次 p oll () 方法 等到 分配到分区之后才可以重置消费位置

六、控制或关闭消费

Kafka Consumer 提供了对消费速度进行控制的方法,在有些应用场景下我 们可能 需要暂停某些分区的消费而先消费其他分区,当达到一定条件时再恢复这些分区的消费。 Ka fkaConsumer 中使用 pa use () res ume  ()方法来分别实现暂停某些分区在拉取操作时返回数据给客户端和恢复 某些分区向客户端返回数据的操作

七、再均衡

再均衡是指分区的所属权从 个消费者转移到另 消费者的行为, 它为消费组具备高可用 性和伸缩性提供保障, 使我们可以既方便又安全地删除消费组内的消费者或往消费组内添加消 费者。 不过在再均衡发生期间, 消费组内的消费者是无法读取消息的。 也就是说, 在再均衡发 生期间的这 一 小段时间内, 消费组会变得不可用。 另外, 当 个分区被重新分配给另 个消费者时, 消费者当前的状态也会丢失。 比如消费者消费完某个分区中的 一 部分消息时还没有来得 及提交消费位移就发生了再均衡操作, 之后这个分区又被分配给了消费组内的另 一 个消费者, 原来被消费完的那部分消息又被重新消费 一 遍, 也就是发生了重复消费。 般情况下, 应尽量 避免不必要的再均衡的发生
Kafka的API允许我们在消费者新增分区或者失去分区时进行处理,我们只需要在调用subscribe()方法时传入ConsumerRebalanceListener对象,该对象有两个方法:
  • public void onPartitionRevoked(Collection partitions):此方法会在消费者停止消费消费后,在重平衡开始前调用。
  • public void onPartitionAssigned(Collection partitions):此方法在分区分配给消费者后,在消费者开始读取消息前调用。

我们也可以在消费组停止前将记录和偏移量存到db中,然后在分配分区之后调用seek() 方法将存入库中的偏移量设置进去!这样可以防止消费者发生在均衡时造成的消息丢失或者消息重复!

八、消费者拦截器

与生产者拦截器类似,略过

九、线程安全

KatkaProducer是线程安全的, 然而KafkaConsumer却是非线程安全的。 KafkaConsumer中 定义了 一 个acquire()方法, 用来检测当前是否只有 个线程在操作, 若有其他线程正在操作则 会抛出ConcurrentModifcationException异常:
Kafka Consumer中的每个公用方法在执行所要执行的动作之前都会调用这个acquire()方法, 只有wakeup()方法是个例外
如果想要多线程消费消息可以先使用单线程poll消息然后多线程去处理业务逻辑

十、重要的消费者参数

1.fetch .min .bytes 和fetch .max.bytes

该参数用来配置 Co nsumer 在一次拉取请求(调用 poll () 方法)中能从 Ka fka 中拉取的最小/大数据量,最小默认值为1(b),最大默认值为50MB,Kafka 在收到 Consumer 拉取请求时,如果返回给 Cons umer 的数据量小于/大于这个参数所配置的值,那么它就需要进行等待,直到数据量满足这个参数的配置大小。 可以适当调大这个参数的值以提高 一定 的吞吐量,不过也 会造成额外的延迟( late ncy ,对于 延迟敏感的应用可能就不可取了。

2.fetch.max.wait.ms 

用于指定 Kafka 的等待时间

3.max.patition.fetch.bytes

这个参数用来配置从每个分区里返回给 Con sumer 的最大数据 ,默认值1 MB

4.max.poll.records

用来配置 Consumer 次拉取请求中拉取的最大消息数,默认值为 500 (条)。

5.receive.buffer.bytes和send bu er.bytes

设置 Socket 输入/接收消息缓冲区( 接受窗口)大小,

6.isolation.level

这个参数用来配置消费者的事务隔离级别。字符串类型,有效值为“ read uncommitted ,,和 “ read comm itted ",表示消费者所消费到的位置,如果设置为“ read committed ”,那么消费 者就会忽略事务未提交的消息,即只能消费到 LSO ( LastStableOffset )的位置,默认情况下为 “ rea d _ uncommitted ”,即可以消 费到 HW (High Watermark )处的位置

十一、消费者常见问题:

1.丢失消息:

先commit再处理消息。如果在处理消息的时候异常了,但是offset 已经提交了,这条消息对于该消费者来说就是丢失了,再也不会消费到了

解决方案:手动提交并且同步提交

2.消费到重复消息

网络拥塞导致 producer 误以为发送错误,导致重试,从而产生重复数据,可以通过幂等性配置避免。

 3.消费到的数据乱序

参数配置 max.in.flight.requests.per.connection = 1 ,但同时会限制 producer 未响应请求的数量,即造成在 broker 响应之前,producer 无法再向该 broker 发送数据。
 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值