Kafka学习(四):Consumer

125 阅读5分钟

Consumer

参考

www.cnblogs.com/huangdh/p/1…

Push vs Pull

Pull由consumer控制拉取速率,有堆积是堆积在kafka一侧,能反压起码能告警。

Push由broker控制发送速率,consumer来不及处理不就报错然后数据马上就丢了?

参数与流程

参数配置

这里先列一下参数,可以由参数来看流程

参数描述
group.id消费者所属的消费者组
enable.auto.commitconsumer周期性自动向broker提交offset
auto.commit.interval.ms上面这个周期的值,默认5s
session.timeout.ms消费者组协调器在认定消费者失效前的等待时间
heartbeat.interval.ms消费者发送心跳给组协调器的时间间隔
auto.offset.resetearliest:自动重置偏移量到最早的偏移量;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消费。

image.png

初始化和消费流程

// 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分配与再平衡

image.png

上图展示的这样,如果是有多个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提交

自动提交

image.png

手动提交

由消费者显式调用 commitSync 或 commitAsync 方法来提交当前的消费位移。

commitSync(同步提交):必须等待offset提交完毕,再去消费下一批数据。同步提交offset更可靠一些,但是由于其会阻塞当前线程,直到提交成功,因此吞吐量会受到很大的影响。

commitAsync(异步提交):发送完提交offset请求后,就开始消费下一批数据了。

指定offset

earliest:自动将偏移量重置为最早的偏移量,--from-beginning。

latest(默认值):自动将偏移量重置为最新偏移量。

none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。

重复消费与漏消费

image.png

重复消费:间隔5s自动提交时,上图中的消费者,在第2s挂掉了,恢复的时候,前2s的数据就会被重复消费;某些情况下消费组再平衡也会导致?

重复消费的关键在于处理消息是否时幂等的,如果每次处理结果都一样,那重复就重复呗;如果叠加处理会有不同的效果,那就有问题了。

漏消费:如果消息堆积保留的时间太长,长到过期了;或者consumer在处理过程中出现啥异常(至少一次的没收到确认还会继续继续消费),消息就会被漏掉。

每个topic有个retention.ms配置,过了这个保留时间数据就会被标记为不可用,这时候一般也恢复不了,等到一定时候再统一整理压缩删除,这样做是为了减少IO提高性能。