前言
1、队列重平衡概述
如果对RocketMQ或者对消息中间件有所了解的话,消费端在进行消息消费时至少需要先进行队列(分区)的负载,即一个消费组内的多个消费者如何对订阅的主题中的队列进行负载均衡,当消费者新增或减少、队列增加或减少时能否自动重平衡,做到应用无感知,直接决定了程序伸缩性,其说明图如下:
本节将聚集Kafka消费端的重平衡机制。
2、Kafka消费端基本流程
在介绍kafka消费端重平衡机制之前,我们首先简单来看看消费者拉取消息的流程,从整个流程来看重平衡的触发时机、在整个消费流程中所起的重要作用,消费端拉取消息的简要流程如下图所示:
主要的关键点如下:
- 判断KafkaConsumer对象是否处在多线程环境中。注意:该对象是多线程不安全的,不能有多个 线程持有该对象。
- 消费组初始化,包含了队列负载(重平衡)
- 消息拉取
- 消息消费拦截器处理
关于poll方法的核心无非就是两个:重平衡与消费拉取,本篇文章将重点剖析Kafka消费者的重平衡机制。
3、消费者队列负载(重平衡)机制
通过对updateAssignmentMetadataIfNeeded方法的源码剖析,最终调用的核心方法为ConsumerCoordinator的poll方法,核心流程图如下:
消费者协调器的核心流程关键点:
- 消费者协调器寻找组协调器
- 队列负载(重平衡)
- 提交位点
本篇文章将深入探讨Kafka的重平衡机制。
在深入研究kafka重平衡机制之前,首先请简单思考如下问题:
- 重平衡会阻塞消息消费吗?
- Kafka的加入组协议哪些变更能有效减少重平衡
架构思维修炼:针对第二个问题,作为一名从源码级别去解读Kafka,深入思考其内部的原理是架构师的一种必备素质。
3.1 消费者协调器
在Kafka中,在客户端每一个消费者会对应一个消费者协调器(ConsumerCoordinator),在服务端每一个broker会启动一个组协调器。
接下来将对该过程进行源码级别的跟踪,根据源码提练工作机制,该部分对应上面流程图中的:ensureCoordinatorReady方法。
该方法的关键点如下:
- 首先判断一下当前消费者是否已找到broker端的组协调器,如果已感知,则返回true。
- 如果当前并没有感知组协调器,则向服务端(broker)寻找该消费组的组协调器。
- 寻找组协调器的过程是一个同步过程,如果出现异常,则会触发重试,但引入了重试间隔机制。
- 如果未超时并且没有获取组协调器,则再次尝试(do while)。
核心要点为lookupCoordinator方法,该方法的核心是选择一台负载最小的broker,构建请求,向broker端查询消费组的组协调器,代码如下:
查询组协调器的请求,核心参数为:
- ApiKeys apiKey
请求API,类比RocketMQ的RequestCode,根据该值很容易找到服务端对应的处理代码,这里为ApiKeys.FIND_COORDINATOR。
- String coordinatorKey
协调器key,取消费组名称。
思考题:提前剧透一下:Kafka服务端每一台Broker会创建一个组协调器(GroupCoordinator),每一个组协调器可以协调多个消费组,但一个消费组只会被分配给一个组协调器,那这里负载机制是什么呢?服务端众多Broker如何竞争该消费组的控制权呢?
- coordinatorType 协调器类型,默认为GROUP,表示普通消费组。
- short minVersion 版本。
针对客户端端请求,服务端统一入口为KafkaApis.scala,可以根据ApiKeys快速找到其处理入口,如图所示: