Consumer
参考
Push vs Pull
Pull由consumer控制拉取速率,有堆积是堆积在kafka一侧,能反压起码能告警。
Push由broker控制发送速率,consumer来不及处理不就报错然后数据马上就丢了?
参数与流程
参数配置
这里先列一下参数,可以由参数来看流程
| 参数 | 描述 |
|---|---|
| group.id | 消费者所属的消费者组 |
| enable.auto.commit | consumer周期性自动向broker提交offset |
| auto.commit.interval.ms | 上面这个周期的值,默认5s |
| session.timeout.ms | 消费者组协调器在认定消费者失效前的等待时间 |
| heartbeat.interval.ms | 消费者发送心跳给组协调器的时间间隔 |
| auto.offset.reset | earliest:自动重置偏移量到最早的偏移量;latest:默认,自动重置偏移量为最新的偏移量。 |
| max.poll.records | 一次poll拉取数据返回消息的最大条数,默认是500条。 |
| fetch.max.bytes | 默认50m,但不是绝对限制,也受broker配置message.max.bytes或topic配置max.message.bytes影响 |
| max.poll.interval.ms | 消费者处理消息的最大时长,默认5分钟。超过该值,该消费者被移除,消费者组执行再平衡。 |
| partition.assignment.strategy | 指定消费者组分区分配策略(如 Range, RoundRobin 等) |
| client.id | 标识 Kafka 客户端;在 Kafka 监控中有用 |
消费者组
一个consumer可以消费多个partition,但一个partition只能被一个consumer消费。
初始化和消费流程
// 1、创建KafkaConsuemr实例
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
// 2、初始化Kafka Client
// 在 KafkaConsumer 的构造函数中,会初始化 Kafka 客户端
this.client = new KafkaConsumerNode(/* ...parameters... */);
// 3、启动ConsumerCoordinator
// 使用 group.id,session.timeout.ms,heartbeat.interval.ms 等参数来启动协调器,并管理心跳、消费者状态等。
this.coordinator = new ConsumerCoordinator(/* ...parameters... */);
// 4、加入消费组
// 在消费者启动后,它会调用 ConsumerCoordinator 的 ensureActiveGroup 方法,使自己加入消费者组
public void ensureActiveGroup() {
while (this.state.hasNotJoinedGroup()) {
ensureCoordinatorReady();
if (needRejoin()) {
joinGroupIfNeeded();
}
}
}
private void joinGroupIfNeeded(long now) {
// 如果不需要重新加入组,直接返回
if (!rejoinNeededOrPending()) {
return;
}
// 确保协调器可用
if (coordinatorUnknown()) {
ensureCoordinatorReady();
}
while (rejoinNeededOrPending()) {
// 准备加入组
maybeLeaveGroup();
// 获取加入组请求
JoinGroupResponse response = sendJoinGroupRequest();
// 处理加入组响应
processJoinGroupResponse(response);
// 如果需要再平衡,继续处理
if (rejoinNeededOrPending()) {
maybeCompletePreviousRebalance();
}
}
}
// 1、订阅主题
// 调用 subscribe 方法,消费者会根据 group.id,partition.assignment.strategy 等参数与协调器通信。
consumer.subscribe(Arrays.asList("your_topic"));
// 2、拉取消息
// 调用 poll 方法,根据 max.poll.records, fetch.min.bytes, fetch.max.wait.ms 等参数控制拉取消息行为。
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
// 3、提交位移
// 使用 commitSync 或 commitAsync,根据 enable.auto.commit, auto.commit.interval.ms 等参数提交消费位移。
consumer.commitSync();
分区分配与再平衡
range分配与再平衡
上图展示的这样,如果是有多个topic的分区都除不尽的话,range分配就容易产生数据倾斜。
RoundRobin分配与再平衡:针对集群中所有Topic而言。把所有的partition 和所有的consumer 都列出来,然后按照hashcode进行排序,最后通过轮询算法来分配partition 给各个消费者,平均分基本上是。
StickyAssignor: 保证分区分配的稳定性,尽量保持分区的分配不发生变化。
// 当再平衡触发时,协调器会调用 `joingroup` RPC,使消费者加入组并接收分配的分区
private void joinGroupIfNeeded(long now) {
// Ensure coordinator is ready
ensureCoordinatorReady()
// handle rebalance
while (rejoinNeededOrPending()) {
maybeLeaveGroup();
JoinGroupResponse response = sendJoinGroupRequest();
processJoinGroupResponse(response);
}
}
// 消费者使用 `AbstractCoordinator` 注册的分配策略进行分区分配,例如 Range、RoundRobin 或 Sticky
protected Map<String, Assignment> performAssignment(String leaderId, Generation generation, Map<String, List<String>> groupAssignment) {
PartitionAssignor assignor = partitionAssignors.get(0);
return assignor.assign(new GroupSubscription(groupAssignment)).groupAssignment();
}
public Map<String, Assignment> assign(Cluster metadata, GroupSubscription groupSubscription) {
//具体的策略实现
}
// 当消费者组中的所有消费者都收到分区分配结果后,调用 `onJoinComplete` 方法完成分配。
protected synchronized void onJoinComplete(JoinGroupResponse response, Metadata metadata) {
// Ensure assignment matches local state
assignPartitions(response);
// Notify listeners about assignment
assignmentListener.onPartitionAssignment(newAssignment);
}
offset
Kafka 中的位移(offset)是消费者跟踪被消费消息的记录,这对于确保消息被正确消费且不被重复消费或者漏消费至关重要。
Offset: 消息在分区中的位置,每个分区都有其独立的 offset 值。offset 是一个递增的长整型数,每次新消息写入分区时其 offset 都会增加。
Consumer Group Offset Management: 在消费组中,每个消费者需要管理自己已消费消息的 offset,以确保当再平衡或故障恢复时能够继续消费未处理的消息。
offset提交
自动提交
手动提交
由消费者显式调用 commitSync 或 commitAsync 方法来提交当前的消费位移。
commitSync(同步提交):必须等待offset提交完毕,再去消费下一批数据。同步提交offset更可靠一些,但是由于其会阻塞当前线程,直到提交成功,因此吞吐量会受到很大的影响。
commitAsync(异步提交):发送完提交offset请求后,就开始消费下一批数据了。
指定offset
earliest:自动将偏移量重置为最早的偏移量,--from-beginning。
latest(默认值):自动将偏移量重置为最新偏移量。
none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。
重复消费与漏消费
重复消费:间隔5s自动提交时,上图中的消费者,在第2s挂掉了,恢复的时候,前2s的数据就会被重复消费;某些情况下消费组再平衡也会导致?
重复消费的关键在于处理消息是否时幂等的,如果每次处理结果都一样,那重复就重复呗;如果叠加处理会有不同的效果,那就有问题了。
漏消费:如果消息堆积保留的时间太长,长到过期了;或者consumer在处理过程中出现啥异常(至少一次的没收到确认还会继续继续消费),消息就会被漏掉。
每个topic有个retention.ms配置,过了这个保留时间数据就会被标记为不可用,这时候一般也恢复不了,等到一定时候再统一整理压缩删除,这样做是为了减少IO提高性能。