Kafka003——聊聊Consumer

173 阅读8分钟

写在前面

这次的文章我尝试使用了New Bing(自己的VPS IP被ChatGPT封了)。在使用New Bing的过程中,感觉挺奇妙的,我是在把它当做面试者来提问的。AI的进步是巨大的。在面对问题,它的回答准确而且有框架。New Bing还会结合搜索引擎的结果来作答,这可以让提问的人能继续根据兴趣点向深处挖掘。

Consumer是什么?

以Kafka为例(以下皆是),Consumer是Kafka重要的组成部分,它可以从Kafka的Topic中读取消息并做处理。Consumer是消息消费能力的实现者。它可以单独存在,也可以由多个Consumer组合成Consumer Group,以此来实现消费能力的横向拓展。

Consumer的生命周期是怎样的

Kafka的Consumer的生命周期从创建到关闭,整个过程可以大概分为以下几个阶段:

  1. 初始化:Consumer在Kafka中是一个类,其创建也就是对应类的实例,需要设置相关的参数去影响Consumer消费消息的行为和性能。
  2. 订阅:Consumer通过subscribe()方法,指定要消费的Topic,并获取Broker分配的Partition。除了subscribe()方法,还可以通过正则表达式来实现动态监听满足条件的多个Topic,或者通过实现ConsumerRebalanceListener来动态地订阅主题和分区。
  3. 拉取:Consumer通过poll()方法,从Broker获取批量的消息。就像鲨鱼需要一直游动才不会窒息死亡,Consumer也需要不停调用poll()方法来维持活跃、获取消息。poll()方法除了拉取批量消息以外,还需要给Broker的Group Coordinator发送HeartBeat,用来维持Group内的成员关系。Rebalance过程中,Group Coordinator也会通过poll()方法向Consumer传达如:Rebalance开始、Group Leader、重分配的Partition等信息。
  4. 消费:Consumer根据自身业务需求,对拉取到的消息进行各种业务逻辑处理、如:数据存储、消息加工、调用下游、数据计算等。消费需要注意Consumer是批量获取的消息,批量处理的整体时长正常时不能超过 max.poll.interval.ms 的间隔,不然这会导致Rebalance的发生。
  5. 提交:Consumer在处理完消息之后,需要通过 commitSync() 或 commitAsync() 方法向Broker提交消费完成的offset。offset被Broker用来记录消息被成功消费了的位置信息,用于重启或故障恢复时继续消费新的消息。可以通过enable.auto.commit参数来自动或手动提交offset。手动提交offset会有更好的灵活性。
  6. 关闭:在不需要消费数据时,调用close()方法,关闭KafkaConsumer对象。这会释放资源,取消订阅,提交位移,离开消费者组等。

初始化

Consumer的初始化就是KafkaConsumer类的实例化。实例化时,有以下几个重要的参数可以注意:

  1. bootstrap.servers:这个参数除了在Consumer的初始化时会用到,Producer初始化时也会用到。它是用于发现Kafka集群信息的。它形态上是以“,”连接的多个“broker:port”字符串。
  2. key.deserializer 与 value.deserializer:用于将消息的key与value从二进制字节流反序列化为对象的方式。消息的key用于唯一定义一条消息,决定这条消息被生成到Topic里的哪个Partition,消息的value对应的是实际生成的消息内容。
  3. group.id:用于判断Consumer是否属于同一个Group的参数。
  4. fetch.min.bytes:设置Consumer从Kafka Fetch的最小消息大小。当Topic没有满足该参数大小的消息时,poll会阻塞。
  5. fetch.max.wait.ms:Consumer等待Kafka返回消息的最大等待时间,默认值:500ms。该参数与 fetch.min.bytes 会共同影响Kafka返回消息给Consumer的时机。
  6. max.partition.fetch.bytes:该参数控制Kafka允许的Topic的每个Partition能返回的最大消息大小。该参数乘以Partition数量,相当于Topic消费的最大消息总量,再除以Consumer的个数,便是每个Consumer需要承载的最大消息大小。
  7. session.timeout.ms:和Rebalance中介绍的一样,该参数是Consumer与Broker维持会话的最大超时时间,默认10s,一般设置为Consumer HeartBeat心跳间隔的3~5倍。
  8. auto.offset.reset:该参数用于设定Consumer初始消费位置。如:latest:从最新位置开始消费;earliest:从最老的消息开始消费。此外,可以通过运维脚本来修改这个值。
  9. enable.auto.commit:用于指定是否启用自动提交偏移量的机制,若为true,则Consumer会在每次 poll 之后或者每隔一段时间(auto.commit.interval.ms设定)就提交最大的offset给 kafka;若为false,则Consumer需要通过commitSync 或者 commitAsync 方法来提交offset
  10. partition.assignment.strategy:分区分配策略,可见下面。
  11. max.poll.records:Consumer的poll()方法返回的最大消息数量。

具体来说,常见的Consumer初始化步骤如下:

  1. 构造 Propertity,进行 consumer 相关的配置;
  2. 创建 KafkaConsumer 的对象 consumer;
  3. 订阅相应的 topic 列表;
  4. 调用 consumer 的 poll 方法拉取订阅的消息。

订阅消息

consumer通过 subcribe() 方法订阅Topic。这个方法可以接受多个topic作为一个list,或者一个正则表达式来匹配多个Topic。

consumer订阅topic之后,会加入到Consumer Group之中。一个Group内的多个Consumer会负载均衡地消费Topic内的多个Partition。Kafka的分区分配策略是:

  1. RangeAssignor:按照Consumer数量与Partition数量进行均分。
  2. RoundRobinAssignor:将Consumer与订阅的Topic所有分区分别按照字典序进行排序,然后通过轮询的方式将每个分区逐个分配给Consumer,
  3. StickyAssignor:该策略有两个目的:1. 尽可能均分分区;2. 尽可能保障每次分配的结果变化不大。这个逻辑比较复杂,就先按下不表。

poll模型

poll方法是consumer的心脏。poll方法会处理Group Coordinate、Rebalance、HeartBeat、消息抓取、离开Group等的所有信息。consumer在创建之后,也是在poll之后,才会去连接到Kafak的实际集群。

首先大概看一下poll方法的原理。它主要做了以下几件事情:

  1. 检查Consumer是否有订阅Topic与Partition;如果没有,这里会抛出异常;
  2. 调用实际的 pollOnce() 方法获取消息records;
  3. 返回数据之前,发送下次的fetch()请求,避免下次请求时被pullOnce阻塞;
  4. 如果在一定时间内没有获取到数据,则返回拉取的records为空;

pollOnce()方法大概做了以下几件事情:

  1. 获取GroupCoordinator的地址,与之建立相应的TCP连接。发送JoinGroup与SyncGroup请求。至此Consumer完成了与Kafka集群的连接,并加入了一个ConsumerGroup。
  2. 更新需要拉取的Partition的fetch-position offset,准备拉取消息。
  3. 获取fetcher拉取的消息,如果此时获取到消息,直接返回。如果为空则需要重新发送sendFetch的请求。
  4. 调用底层NetworkClient的poll方法,等待send的fetch完成所有消息的抓取。
  5. Rebalance检查:判断当前Consumer分配的Topic-Partition信息是否有变化。有的话,这次poll方法直接返回空,Consumer等待GroupCoordinator发起Rebalance流程。

消费

消息就是Consumer代表的实际业务逻辑了。在风控场景中,一般会有风控物料组装、风控模型请求、策略逻辑计算、策略日志留存等等。

提交

Consumer在完成消费时,会向Kafka同步自己完成消费的消息的位置,这个位置会保留在Partition中,用于记录这个分区消息消费的进度。

Consumer完成commit offset有如下几种方法:

  1. 自动Commit:通过配置 enable.auto.commit=true 与设置 auto.commit.interval.ms 自动commit间隔,来控制这个自动的过程。自动commit机制也与poll模型绑定,当Consumer每次poll时,Consumer便会检查是否满足自动Commit的要求。
  2. 手动Commit:通过CommitSync与CommitAsync来实现同步与异步Commit。在Consumer完成消息消费之后,如果调用CommitSync,那么会阻塞消费进程直到Commit成功。而如果使用CommitAsync,则会异步触发,立即开始下一次poll。CommitAsync可以通过Callback方法,实现异步Commit失败时的感知。也可以考虑结合使用两种Commit方式,如每次批处理完成后,使用异步方法,不阻塞下一次批处理,提升Consumer处理效率;但当Consumer退出或者程序异常时,使用同步Commit,确保Consumer最新的消费进度能被同步到Partition上。

关闭

Consumer的关闭,也可以说退出Group,从使用上来看,会有以下几种情况:

  1. 通过consumer.Close() 或者 consumer.Shutdown()方法,关闭网络连接与socket,触发Rebalance。但Close()方法会完成offset的提交。
  2. 还可以通过设置Consumer未接收新消息的时间来完成Consumer的关闭,没使用过,这里不深入。

Consumer退出时,大概会做以下几件事:

  1. 向GroupCoordinator发送一个 LeaveGroupRequest,告知自己要离开Group。
  2. GroupCoordinator 收到 LeaveGroupRequest 后,会检查Consumer是否是Group的成员,如果是,就将其从成员列表中移除,并更新Group的元数据。
  3. GroupCoordinator 还会检查消费者是否是消费组的 leader,如果是,就会从剩余的成员中选择一个新的 leader,并通知它。
  4. GroupCoordinator 还会检查Group是否还有其他成员,如果没有,就会删除消费组的元数据,并释放相关的资源。
  5. GroupCoordinator 还会触发一次Rebalance,将原来由Consumer负责的分区分配给其他消费者。
  6. Consumer关闭时,还会关闭网络连接和套接字,释放所有的资源,并根据配置,提交最后的 offset 到存储(Kafka 或 Zookeeper)。

参考资料

  1. Chapter 4. Kafka Consumers: Reading Data from Kafka
  2. 你真的了解bootstrap.servers这个参数么?
  3. Kafka 源码解析之 Consumer Poll 模型(七) - 知乎 (zhihu.com)