1、Consumer与Consumer Group
consumer group是kafka提供的可扩展且具有容错性的消费者机制。组内可以有多个消费者或消费者实例(consumer instance),它们共享一个公共的ID,即group ID。组内的所有消费者协调在一起来消费订阅主题(subscribed topics)的所有分区(partition)。
consumer group下可以有一个或多个consumer instance,consumer instance可以是一个进程,也可以是一个线程。
group.id是一个字符串,唯一标识一个consumer group。
consumer group下订阅的topic下的每个分区只能分配给某个group下的一个consumer(当然该分区还可以被分配给其他group)。
2、Coordinator介绍
Coordinator一般指的是运行在broker上的group Coordinator,用于管理Consumer Group中各个成员,每个KafkaServer都有一个GroupCoordinator实例,管理多个消费者组,主要用于offset位移管理和Consumer Rebalance。
Coordinator存储的信息
对于每个Consumer Group,Coordinator会存储以下信息:
- 对每个存在的topic,可以有多个消费组group订阅同一个topic(对应消息系统中的广播)
- 对每个Consumer Group,元数据如下:
- 订阅的topics列表
- Consumer Group配置信息,包括session timeout等
- 组中每个Consumer的元数据。包括主机名,consumer id
- 每个正在消费的topic partition的当前offsets
- Partition的ownership元数据,包括consumer消费的partitions映射关系
如何确定consumer group的coordinator
consumer group如何确定自己的coordinator是谁呢? 简单来说分为两步:
- 确定consumer group位移信息写入__consumers_offsets这个topic的哪个分区。具体计算公式:
__consumers_offsets partition# = Math.abs(groupId.hashCode() % groupMetadataTopicPartitionCount) 注意:groupMetadataTopicPartitionCount由offsets.topic.num.partitions指定,默认是50个分区。 - 该分区leader所在的broker就是被选定的coordinator
3、offset位移管理
消费者在消费的过程中需要记录自己消费了多少数据,即消费位置信息。在Kafka中这个位置信息有个专门的术语:位移(offset)。
(1)很多消息引擎都把这部分信息保存在服务器端(broker端)。这样做的好处当然是实现简单,但会有三个主要的问题:
- broker从此变成有状态的,会影响伸缩性
- 需要引入应答机制(acknowledgement)来确认消费成功
- 由于要保存很多consumer的offset信息,必然引入复杂的数据结构,造成资源浪费
而Kafka选择了不同的方式:每个consumer group管理自己的位移信息,那么只需要简单的一个整数表示位置就够了;同时可以引入checkpoint机制定期持久化,简化了应答机制的实现。
(2)Kafka默认是定期帮你自动提交位移的(enable.auto.commit = true),你当然可以选择手动提交位移实现自己控制。
(3)另外kafka会定期把group消费情况保存起来,做成一个offset map,如下图所示:
offset管理
老版本的位移是提交到zookeeper中的,目录结构是:/consumers/<group.id>/offsets/<topic>/<partitionId>,但是zookeeper其实并不适合进行大批量的读写操作,尤其是写操作。
因此kafka提供了另一种解决方案:增加__consumeroffsets topic,将offset信息写入这个topic,摆脱对zookeeper的依赖(指保存offset这件事情)。__consumer_offsets中的消息保存了每个consumer group某一时刻提交的offset信息。依然以上图中的consumer group为例,格式大概如下:- ZookeeperOffsetManager: 调用zookeeper来存储和接收offset(老版本的位移管理)
- DefaultOffsetManager: 提供消费者offsets内置的offset管理
DefaultOffsetManager
Offset Commit提交过程:
Offset Fetch获取过程:
- 接收请求的broker首先决定”offset topic”的哪个partition负责这个请求
- 从broker的leader cache中找出对应partition的leader(会在controller的每次metadata更新请求中更新缓存)
- 如果接收请求的broker就是leader,它会从自己的offset manager中读取出offset,并添加到响应中
- 如果offset不存在,返回UnknownTopicOrPartitionCode
- 如果broker正在加载offsets table,返回OffsetLoadingCode.消费者受到这个状态码会在之后重试
- 如果接收请求的broker不是指定topic-partition的leader,它会将OffsetFetchRequest转发给这个partition的当前leader
- 如果”offsets topic”这个时候不存在,它会尝试自动创建,在创建成果后,会返回offset=-1
4、Consumer Reblance
什么是rebalance?
rebalance本质上是一种协议,规定了一个consumer group下的所有consumer如何达成一致来分配订阅topic的每个分区。比如某个group下有20个consumer,它订阅了一个具有100个分区的topic。正常情况下,Kafka平均会为每个consumer分配5个分区。这个分配的过程就叫rebalance。Kafka新版本consumer默认提供了两种分配策略:range和round-robin。
rebalance的触发条件有三种:
- 组成员发生变更(新consumer加入组、已有consumer主动离开组或已有consumer崩溃了——这两者的区别后面会谈到)
- 订阅主题数发生变更——这当然是可能的,如果你使用了正则表达式的方式进行订阅,那么新建匹配正则表达式的topic就会触发rebalance
- 订阅主题的分区数发生变更
Rebalance Generation
- Heartbeat请求:consumer需要定期给coordinator发送心跳来表明自己还活着
- LeaveGroup请求:主动告诉coordinator我要离开consumer group
- SyncGroup请求:group leader把分配方案告诉组内所有成员
- JoinGroup请求:成员请求加入组
- DescribeGroup请求:显示组的所有信息,包括成员信息,协议名称,分配方案,订阅信息等。通常该请求是给管理员使用
Coordinator在rebalance的时候主要用到了前面4种请求。
liveness
consumer如何向coordinator证明自己还活着? 通过定时向coordinator发送Heartbeat请求。如果超过了设定的超时时间,那么coordinator就认为这个consumer已经挂了。一旦coordinator认为某个consumer挂了,那么它就会开启新一轮rebalance,并且在当前其他consumer的心跳response中添加“REBALANCE_IN_PROGRESS”,告诉其他consumer:不好意思各位,你们重新申请加入组吧!
Rebalance过程
- Dead:组内已经没有任何成员的最终状态,组的元数据也已经被coordinator移除了。这种状态响应各种请求都是一个response: UNKNOWN_MEMBER_ID
- Empty:组内无成员,但是位移信息还没有过期。这种状态只能响应JoinGroup请求
- PreparingRebalance:组准备开启新的rebalance,等待成员加入
- AwaitingSync:正在等待leader consumer将分配方案传给各个成员
- Stable:rebalance完成!可以开始消费了
rebalance场景剖析
1、新成员加入组(member join)
4、提交位移(member commit offset)