聊聊 Kafka Consumer 基础

242 阅读6分钟

🕒携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天

今天聊聊 Kafka Consumer 即消费者的内部底层原理设计思想。

01 Consumer之总体概述

在 Kafka 中, 我们把消费消息的一方称为 Consumer 即 消费者, 它是 Kafka 的核心组件之一。它的主要功能是将 Producer 生产的消息进行消费处理,完成消费任务。那么这些 Producer 产生的消息是怎么被 Consumer 消费的呢?又是基于何种消费方式进行消费,分区分配策略都有哪些,消费者组以及重平衡机制是如何处理的,偏移量如何提交和存储,消费进度如何监控, 如何保证消费处理完成?接下来会逐一讲解说明。

02 Consumer之消费方式详解

我们知道消息队列一般有两种实现方式,(1)Push(推模式) (2)Pull(拉模式),那么 Kafka Consumer 究竟采用哪种方式进行消费的呢?其实 Kafka Consumer 采用的是主动拉取 Broker 数据进行消费的即 Pull 模式。这两种方式各有优劣,我们来分析一下:

1)、为什么不采用Push模式?如果是选择 Push 模式最大缺点就是 Broker 不清楚 Consumer 的消费速度,且推送速率是 Broker 进行控制的, 这样很容易造成消息堆积,如果 Consumer 中执行的任务操作是比较耗时的,那么 Consumer 就会处理的很慢, 严重情况可能会导致系统 Crash。

2)、为什么采用Pull模式?如果选择 Pull 模式,这时 Consumer 可以根据自己的情况和状态来拉取数据, 也可以进行延迟处理。但是 Pull 模式也有不足,Kafka 又是如何解决这一问题?如果 Kafka Broker 没有消息,这时每次 Consumer 拉取的都是空数据, 可能会一直循环返回空数据。 针对这个问题,Consumer 在每次调用 Poll() 消费数据的时候,顺带一个 timeout 参数,当返回空数据的时候,会在 Long Polling 中进行阻塞,等待 timeout 再去消费,直到数据到达。

03 Consumer之初始化

聊完 Consumer 消费方式和优缺点以及 Kafka 针对缺点又是如何权衡解决的,接下来我们来聊聊 Consumer初始化都做了什么?

Kafka consumer 初始化代码:

从代码可以看出初始化 Consumer 有4步:

1构造 Propertity 对象,进行 Consumer 相关的配置;

2 创建 KafkaConsumer 的对象 Consumer;

3 订阅相应的 Topic 列表;

4 调用 Consumer 的 poll() 方法拉取订阅的消息

Kafka consumer 消费流程图如下:

04 Consumer之消费者组机制

04.1 Consumer Group机制

聊完 Consumer 的初始化流程,接下来我们来聊聊 Consumer 消费者组机制,为什么 Kafka 要设计 Consumer Group, 只有 Consumer 不可以吗? 我们知道 Kafka 是一款高吞吐量,低延迟,高并发, 高可扩展性的消息队列产品, 那么如果某个 Topic 拥有数百万到数千万的数据量, 仅仅依靠 Consumer 进程消费, 消费速度可想而知, 所以需要一个扩展性较好的机制来保障消费进度, 这个时候 Consumer Group 应运而生, Consumer Group 是 Kafka 提供的可扩展且具有容错性的消费者机制。 Kafka Consumer Group 特点如下:

1每个 Consumer Group 有一个或者多个 Consumer

2每个 Consumer Group 拥有一个公共且唯一的 Group ID

3Consumer Group 在消费 Topic 的时候,Topic 的每个 Partition 只能分配给组内的某个 Consumer,只要被任何 Consumer 消费一次, 那么这条数据就可以认为被当前 Consumer Group 消费成功

04.2 Partition 分配策略机制

我们知道一个 Consumer Group 中有多个 Consumer,一个 Topic 也有多个 Partition,所以必然会涉及到 Partition 的分配问题: 确定哪个 Partition 由哪个 Consumer 来消费的问题。

Kafka 客户端提供了3 种分区分配策略:RangeAssignor、RoundRobinAssignor 和 StickyAssignor,前两种分配方案相对简单一些StickyAssignor 分配方案相对复杂一些。

RangeAssignor

RangeAssignor 是 Kafka 默认的分区分配算法,它是按照 Topic 的维度进行分配的,对于每个 Topic,首先对 Partition 按照分区ID进行排序,然后对订阅这个 Topic 的 Consumer Group 的 Consumer 再进行排序,之后尽量均衡的按照范围区段将分区分配给 Consumer。此时可能会造成先分配分区的 Consumer 进程的任务过重(分区数无法被消费者数量整除)。 分区分配场景分析如下图所示(同一个消费者组下的多个 consumer):

结论:这种分配方式明显的问题就是随着消费者订阅的Topic的数量的增加,不均衡的问题会越来越严重。

RoundRobinAssignor

RoundRobinAssignor 的分区分配策略是将 Consumer Group 内订阅的所有 Topic 的 Partition 及所有 Consumer 进行排序后按照顺序尽量均衡的一个一个进行分配。如果 Consumer Group 内,每个 Consumer 订阅都订阅了相同的Topic,那么分配结果是均衡的。如果订阅 Topic 是不同的,那么分配结果是不保证“尽量均衡”的,因为某些 Consumer 可能不参与一些 Topic 的分配。 分区分配场景分析如下图所示:

  1. 当组内每个 Consumer 订阅的 Topic 是相同情况:

  1. 当组内每个订阅的 Topic 是不同情况,这样就可能会造成分区订阅的倾斜:

StickyAssignor

StickyAssignor 分区分配算法是 Kafka Java 客户端提供的分配策略中最复杂的一种,可以通过 partition.assignment.strategy 参数去设置,从 0.11 版本开始引入,目的就是在执行新分配时,尽量在上一次分配结果上少做调整,其主要实现了以下2个目标:

1)、Topic Partition 的分配要尽量均衡。

2)、当 Rebalance(重分配,后面会详细分析) 发生时,尽量与上一次分配结果保持一致。

注意:当两个目标发生冲突的时候,优先保证第一个目标,这样可以使分配更加均匀,其中第一个目标是3种分配策略都尽量去尝试完成的, 而第二个目标才是该算法的精髓所在。

下面我们举例来聊聊 RoundRobinAssignor 跟 StickyAssignor的区别。 分区分配场景分析如下图所示: 1)组内每个 Consumer 订阅的 Topic 是相同情况,RoundRobinAssignor 跟StickyAssignor 分配一致:

当上述情况发生 Rebalance 情况后,可能分配会不太一样,假如这时候C1发生故障下线:

RoundRobinAssignor:

而StickyAssignor:

结论: 从上面 Rebalance 后的结果可以看出,虽然两种分配策略最后都是均匀分配的,但是 RoundRoubinAssignor 完全是重新分配了一遍,而 StickyAssignor 则是在原先的基础上达到了均匀的状态。

  1. 当组内每个 Consumer 订阅的 Topic 是不同情况:

RoundRobinAssignor:

StickyAssignor:

当上述情况发生 Rebalance 情况后,可能分配会不太一样,假如这时候C1发生故障下线:

RoundRobinAssignor:

StickyAssignor:

从上面结果可以看出,RoundRoubin 的分配策略在 Rebalance (重分配)之后造成了严重的分配倾斜。因此在生产环境上如果想要减少重分配带来的开销,可以选用 StickyAssignor 的分区分配策略。

05 总结

至此已经跟大家全面揭秘了 Kafka consumer 的基础知识, 下一篇会讲解 Kafka consumer 的高阶知识, 大家敬请期待......

坚持总结, 持续输出高质量文章 关注我: 华仔聊技术