要想知道如何从 Kafka 读取消息,需要先了解消费者和消费者群组的概念。
消费者和消费者群组
消费者组是为了提升从Kafka消费数据的能力
假设有一个应用程序需要从一个 Kafka Topic读取消息并验证这些消息,然后再把它们保存起来。应用程序需要创建一个消费者对象,订阅主题并开始接收消息,然后验证消息 并保存结果。
当生产者往主题写入消息的速度超过了应用程序验证数据的速度,这个时候该怎么办?
如果只使用单个消费者处理消息,应用程序会远跟不上消息生成 的速度。
显然,此时很有必要对消费者进行横向伸缩。就像多个生产者可以向相同的主题 写入消息一样,我们也可以使用多个消费者从同一个主题读取消息,对消息进行分流。
Kafka 消费者从属于消费者群组。一个群组里的消费者订阅的是同一个主题,每个消费者接收主题一部分分区的消息。
分区和消费者组内消费者个数的关系
假设主题 T1 有 4 个分区,存在消费者 C1,它是群组 G1 里唯一的消费者,用它订阅主题 T1。消费者 C1 将收到主题 T1 全部 4 个分区的消息,如下图所示:
如果在群组 G1 里新增一个消费者 C2,那么每个消费者将分别从两个分区接收消息。假设消费者 C1 接收分区0 和分区 2 的消息,消费者 C2 接收分区1 和分区3 的消息(记住一个partition只能由一个消费者组内的一个消费者消费),如下图所示:
如果群组 G1 有 4 个消费者,那么每个消费者可以分配到一个分区:
如果再往群组里添加更多的消费者,超过主题的分区数量,那么有一部分消费者就会被闲置,不会接收到任何消息,如图所示:
往群组里增加消费者是横向伸缩消费能力的主要方式。
Kafka消费者经常会做一些高延迟操作,比如把数据写到数据库或 HDFS,或使用数据进行比较耗时的计算。
在这些情况下,单个消费者无法跟上数据生成的速度,所以可以增加更多的消费者,让它们分担负载,每个消费者只处理部分分区的消息,这就是横向伸缩的主要手段。
因此有必要为Topic创建大量的分区,在负载增长时可以加入更多的消费者。
不过要注意,不要让消费者的数 量超过主题分区的数量,多余的消费者只会被闲置。
分区与消费者组
除了通过增加消费者来横向伸缩外,还经常出现多个应用程序从同一个主题读取数据的情况——实际上,Kafka 设计的主要目标之一。
在这些场景里,每个应用程序可以获取到所有的消息, 而不只是其中的一部分。
只要保证每个应用程序有自己的消费者群组,就可以让它们获取到主题所有的消息。
不同于传统的消息系统,横向伸缩 Kafka 消费者和消费者群组并不会对性能造成负面影响。
如果在上面例子中新增一个只包含一个消费者的群组 G2,那么这个消费者将从主题 T1 上接收所有的消息,与群组 G1 之间互不影响。同时群组 G2 可以增加更多的消费者,每个消费者可以消费若干个分区,就像群组 G1 那样。总的来说,群组 G2 还是 会接收到所有消息,不管有没有其他群组存在。如下图所示:
简而言之,为每个需要获取一个或多个主题全部消息的应用程序创建一个消费者群组, 然后往群组里添加消费者来伸缩读取能力和处理能力,群组里的每个消费者只处理一部分消息。
消费者群组和分区再均衡
我们知道消费者群组里的消费者共同读取主题的分区。
一个新的消费者加入群组时,它读取的是原本由其他消费者读取的消息。
当一个消费者被关闭或发生崩溃时,它就离开群组,原本由它读取的分区将由群组里的其他消费者来读取。
在Topic发生变化时,比如管理员添加了新的分区,会发生分区重分配。
分区的所有权从一个消费者转移到另一个消费者,这样的行为被称为再均衡。
再均衡非常重要,它为消费者群组带来了高可用性和伸缩性(我们可以放心地添加或移除消费者), 不过在正常情况下,我们并不希望发生这样的行为,因为:
- 在再均衡期间,消费者无法读取消息,造成整个群组一小段时间的不可用。
- 另外,当分区被重新分配给另一个消费者时,消费者当前的读取状态会丢失,它有可能还需要去刷新缓存,在它重新恢复状态之前会拖慢应用程序。
何时发生再均衡
消费者通过向被指派为群组协调器的 broker(不同的群组可以有不同的协调器)发送心跳来维持它们和群组的从属关系以及它们对分区的所有权关系。
只要消费者以正常的时间间隔发送心跳,就被认为是活跃的,说明它还在读取分区里的消息。
消费者会在轮询消息(为了获取消息)或提交偏移量时发送心跳。如果消费者停止发送心跳的时间足够长,会话就会过期,群组协调器认为它已经死亡,就会触发一次再均衡。
如果一个消费者发生崩溃,并停止读取消息,群组协调器会等待几秒钟,确认它死亡了才 会触发再均衡。
在这几秒钟时间里,死掉的消费者不会读取分区里的消息。
在清理消费者时,消费者会通知协调器它将要离开群组,协调器会立即触发一次再均衡,尽量降低处理停顿。
分区分配的过程
当消费者要加入群组时,它会向群组协调器发送一个 JoinGroup 请求。
第一个加入群组的消费者将成为“群主”。
群主从协调器那里获得群组的成员列 表(列表中包含了所有最近发送过心跳的消费者,它们被认为是活跃的), 并负责给每一个消费者分配分区。它使用一个实现了 PartitionAssignor 接口的类来决定哪些分区应该被分配给哪个消费者。
Kafka 内置了两种分配策略,分配完毕之后,群主把分配情况列表发送给群组协调器,协调器再把这些信息发送给所有消费者。
每个消费者只能看到自己的分配信息,只有群主知道群组里所有消费者的分配信息。这个过程会在每次再均衡时重复发生。
版权归原作者 zxu_er 所有, 如有侵权,请联系我们删除。