consumer group
当一个consumer group的多个consumer订阅了一个topic,则每个consumer会接收属于这个topic的partitions的不同子集,这些所有子集不相交且合集为所有的partitions。若consumer的数量超过partition的数量,则会有consumer空闲。当负载增加时,大数量的partition便允许增加更多的consumer来减轻负载。多个consumer group可以订阅同一个topic。
partition rebalance
consumer group中的consumer的增加或减少或订阅的topic改变(比如增加了新的partition)会触发rebalance,rebalance期间无法处理数据,若缓存了数据,则需要刷新缓存。一般的检查是否需要rebalance是在一次长时间的poll过后。若需要rebalance则此次poll返回空值并进行rebalance。
consumer保持为一个group的成员和一个partition的ownership是通过发送heartbeats给指定为group coordinator的kafka broker(不同的consumer group可以有不同的broker),当consumer poll的时候和consume完数据后commit record的时候发送heartbeat。
consumer join a group by sends a joinGroupRequest to the groupCoordinator
第一个加入group的会成为group leader,leader会从groupCoordinator收到一个属于这个group的所有consumer的list(活着的)并负责给每个consumer分配partition,通过partitionAssignor。
分配完后,leader会发送一个分配结果给groupCoordinator,然后groupCoordinator会将该消息发送给所有consumer,每个consumer只会看到自己的分配结果,client进程中只有leader知道所有的分配信息。
consumer可以订阅多个topic,且可以定义要订阅的topic的regular expression,当有新的match this expression的topic被创建,便会触发rebalance,然后consumer会订阅这个topic。
one consumer per thread!!!
max.
partition.fetch.bytes
must be larger than the largest message a broker will accept (determined by the max.message.size
property in the broker configuration)
AUTO.OFFSET.RESET
当consumer读一个它没有connited offset的partition;或它之前有的commited offset无效(consumer挂掉了一段时间)时,default为“latest”即因为缺失有效的offset,consumer会从最新的record开始读,可选项为“earliest”即consumer会读取所有属于这个partition的data,从最早的开始。
PARTITION.ASSIGNMENT.STRATEGY
range:default
分配partitions的一个连续子集,比如consumerC1和C2订阅了topicT1,T2,且T1,T2有三个partition,则C1会被分配到(T1.P0,T1.P1),(T2.P0,T2.P1)而C2会被分配到(T1.P2,T2.P2).
roundRobin:
用订阅到的topic的所有partitions去连续的一个一个分配给consumers,比如consumerC1和C2订阅了topicT1,T2,且T1,T2有三个partition,则C1会被分配到(T1.P0,T1.P2)(T2.P1)而C2会被分配到(T1.P1,)(T2.P0,T2.P2)。
COMMITS AND OFFSETS
automatic commit:
让consumer自动帮你提交,默认值是间隔5秒,它会提交poll()返回的batch record的最后一个record的offset,但如果触发了rebalance,则在上一次提交的offset到触发的时间点时已经处理了的record的offset会被处理两次。可以缩减间隔来减少重复处理的窗口。
commit current offset:
commitSync()可以提交最近的poll()返回的offset。但也会在rebalance后重复处理这其中的数据。
Asynchronous commit:
避免因为broker返回response慢而造成的阻塞和吞吐量的减少。可以减少提交的频率来增加吞吐量,但这也会增加重复处理的窗口大小。
同步提交在提交失败后会重试,但异步提交不会重试。异步可以传入callback来记录错误或重试(这里重试要防止有新的commit提交成功,可以加入一个sequence number,如果sequence number没变,说明没有新的commit提交成功,如果变了说明新的commit提交成功,便不用更新offset了)。
combine sync and async
try{
while(true) {
}
consumer.commitAsync();
} catch (Exception e) {
log.error();
} finally {
try {
consumer.commitSync();
} finally {
consumer.close();
}
}
这样就可以平常偶尔提交失败也没关系,后面新的提交会成功,但在关闭或rebalance后用同步提交来确保提交成功。
commit special offset
上面的commit,都是在处理完batch record后提交最后一个record的offset,但如果batch太大,想一边处理record一边提交,就可以在处理batch records的for循环中加入逻辑,处理完后提交相对应的offset,这时要加上参数表明该offset属于哪个TP。
REBALANCE LISTENERS
用于在consumer失去对partition的所有权时可以加入一些善后的操作,比如提交最新的offset,关闭database连接等等。
CONSUMING RECORD WITH SPECIFIC OFFSETS
可以用于从一个指定的offset开始消费,当这个offset是由kafka以外的一个系统(DB)存储时。
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
currentOffsets.put(
new TopicPartition(record.topic(), record.partition()),
record.offset());
processRecord(record);
storeRecordInDB(record);
consumer.commitAsync(currentOffsets);
}
}
以上代码仍有可能导致record处理完并存入DB,但在commit前crash,造成record被重新处理并且在数据库中存有duplicates。
但可以将record和offset 在一个事务内写入DB,这样我们就知道我们处理完了这个record,并且这个offset被提交了,但这种情况offset是存在DB中而不是kafka,所以可以通过seek()来找到DB中的offset。
EXIT CLEANLY
example code:
它给consumer加入了一个shutdown hook来响应比如ctrl+c的终止指令并在另一个线程调用consumer.wakeup()然后在consumer的循环中抛出wakeupException然后退出consumer的循环。
Standalone Consumer
可以指定一个consumer去订阅固定的topic的固定的partition,而不care任何的rebalance。
订阅模式
subscribe:
使用group动态管理,不能与手动partition管理一起使用,当监控到以下事件的发生,group将会触发rebalance:
- 订阅的topic列表变化
- topic被创建或删除
- consumer group的某个consumer实例挂掉
- 一个新的consumer实例通过join方法加入到一个group中
根据subscriptionState分两类:
- topic列表订阅
- pattern模式订阅
对于pattern模式,每次topic列表变化,若有符合pattern要求的topic加入,便会触发rebalance
assign:
不会使用group管理机制,所以当consumer group member变化或topic的metadata信息变化时是不会触发rebalance操作的。所以需要由用户自己管理自己的处理程序。但可以进行offset commit(要保证group。id的唯一性,如果使用和上面模式一样的group,commit请求会被拒绝)。
consumer加入一个group的过程
GroupCoordinator 是运行在 Kafka Broker 上的一个服务,每台 Broker 在运行时都会启动一个这样的服务,但一个 consumer 具体与哪个 Broker 上这个服务交互,就需要先介绍一下 __consumer_offsets
这个 topic。
__consumer_offsets
是 Kafka 内部使用的一个 topic,专门用来存储 group 消费的情况,默认情况下有50个 partition,每个 partition 三副本。GroupCoordinator 是负责 consumer group member 管理以及 offset 管理。
每个 Consumer Group 都有其对应的 GroupCoordinator,但具体是由哪个 GroupCoordinator 负责与 group.id 的 hash 值有关,通过这个 abs(GroupId.hashCode()) % NumPartitions 来计算出一个值(其中,NumPartitions 是 __consumer_offsets
的 partition 数,默认是50个),这个值代表了 __consumer_offsets
的一个 partition,而这个 partition 的 leader 即为这个 Group 要交互的 GroupCoordinator 所在的节点。
consumer group状态变化图
- 如果 group 是新的 group.id,那么此时 group 初始化的状态为 Empty;
- 当 GroupCoordinator 接收到 consumer 的 join-group 请求后,由于此时这个 group 的 member 列表还是空(group 是新建的,每个 consumer 实例被称为这个 group 的一个 member),第一个加入的 member 将被选为 leader,也就是说,对于一个新的 consumer group 而言,当第一个 consumer 实例加入后将会被选为 leader;
- 如果 GroupCoordinator 接收到 leader 发送 join-group 请求,将会触发 rebalance,group 的状态变为 PreparingRebalance;
- 此时,GroupCoordinator 将会等待一定的时间,如果在一定时间内,接收到 join-group 请求的 consumer 将被认为是依然存活的,此时 group 会变为 AwaitSync 状态,并且 GroupCoordinator 会向这个 group 的所有 member 返回其 response;
- consumer 在接收到 GroupCoordinator 的 response 后,如果这个 consumer 是 group 的 leader,那么这个 consumer 将会负责为整个 group assign partition 订阅安排(默认是按 range 的策略,目前也可选 roundrobin),然后 leader 将分配后的信息以
sendSyncGroupRequest()
请求的方式发给 GroupCoordinator,而作为 follower 的 consumer 实例会发送一个空列表; - GroupCoordinator 在接收到 leader 发来的请求后,会将 assign 的结果返回给所有已经发送 sync-group 请求的 consumer 实例,并且 group 的状态将会转变为 Stable,如果后续再收到 sync-group 请求,由于 group 的状态已经是 Stable,将会直接返回其分配结果
consumerCoordinator负责同步或异步的offsetCommit