初学kafka:一次kafka消费者poll方法的疑问探索,提交位移?消费位移?

1、问题

在kafka中,我们在使用java程序消费者进行消费时,一般都是循环调用poll方法进行拉取数据,在与springboot整合过的消费者也是如此。不知道大家在初学kafka时,会不会有这样的疑问,位移提交设置为手动提交,中间没有生产者生产数据,一直在循环调用poll方法但不进行位移提交,第一次拉取到从上次位移提交未消费的数据,但是再进行循环也是这个poll方法为什么就拉取不到这批数据了呢?重新启动消费者同样也是如此。

java 复制代码
public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "demo02");
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        consumer.subscribe(Arrays.asList("test01"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(2000L));
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("收到的消息:partition= %d,offset= %d,key= %s,value=%s %n", record.partition(),
                        record.offset(), record.key(), record.value());
            }
        }

根据当前问题场景,每次重新启动消费者后会从上次提交位移处拉取数据,并且拉取后不会重复拉取,基本也能猜到kafka消费者应该本地内存中存了自己当前的消费位移,消费者每次请求拉取数据都会传当前的消费位移信息,每次消费者重启后本地存的消费位移也就消失了,启动之后更新为从broker获取的提交位移。

2、猜想验证

利用idea调试功能在拉取到数据前后consumer对象进行了对比。

当前主题中有1条消息未消费,已提交位移为33,拉取到数据前 拉取到数据后 发现了一个看上去很能说明问题的变化,拉取到数据后consumer对象的订阅属性subscriptions中有了明显的变化,多了订阅的主题及分区信息"testo01-0"和位移offset=34,(此时已拉取到offset为33的数据)获取到这些信息之后,大致猜想到offset应该就是消费者本地的消费位移(并非提交位移),并且每次向broker拉取数据时都会携带这个参数;直接对该属性进行修改验证

java 复制代码
int i = 0;
boolean key = false;
while (true) {
    System.out.println("第" + i++ + "次循环");
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(2000L));
    for (ConsumerRecord<String, String> record : records) {
        System.err.printf("收到的消息:partition= %d,offset= %d,key= %s,value=%s %n", record.partition(),
                record.offset(), record.key(), record.value());
        key = true;
    }
    if (key){
        Field subscriptions = consumer.getClass().getDeclaredField("subscriptions");
        subscriptions.setAccessible(true);
        SubscriptionState o = (SubscriptionState) subscriptions.get(consumer);
        System.out.println("subscriptions 对象:" + o);
        Field assignment = o.getClass().getDeclaredField("assignment");
        assignment.setAccessible(true);
        PartitionStates states = (PartitionStates) assignment.get(o);
        List list = states.partitionStateValues();
        Object o1 = list.get(0);
        Field position = o1.getClass().getDeclaredField("position");
        position.setAccessible(true);
        SubscriptionState.FetchPosition o2 = (SubscriptionState.FetchPosition) position.get(o1);
        Field offset = o2.getClass().getDeclaredField("offset");
        offset.setAccessible(true);
        // 每次拉取到数据后都重新设置为33,即要下次拉取的数据从33开始
        offset.set(o2,33L);
    }
}

一直在重复拉取offset为33的数据,至此基本验证了之前的猜想;另外kafka消费者客户端对这部分代码封装的挺好的,想从外部进行修改只能通过反射。

3、poll方法探索

kafka消费者在启动后,会先与broker经过一系列的交互再去拉取数据进行消费 注:图片来源于blog.csdn.net/DraGon_HooR...

在经历过以上步骤之后,消费者客户端会向broker发送一个apiKey=OFFSET_FETCH的请求,大致意思是获取当前消费者在主题分区的上次提交位移,获取到最新的提交位移之后,消费者会将主题分区与位移的对应信息保存在本地内存中。

ini 复制代码
// 请求
RequestHeader(apiKey=OFFSET_FETCH, apiVersion=8, clientId=consumer-demo02-1, correlationId=7)

// 响应  committedOffset=33 消费者已经成功消费了分区中序号为 0 到 32 的消息
OffsetFetchResponseData(throttleTimeMs=0, topics=[], errorCode=0, 
groups=[OffsetFetchResponseGroup(groupId='demo02',
topics=[OffsetFetchResponseTopics(name='test01', 
partitions=[OffsetFetchResponsePartitions(partitionIndex=0, 
committedOffset=33, committedLeaderEpoch=10, 
metadata='', errorCode=0)])], errorCode=0)])

这段代码会在每次消费者拉取到数据后执行

java 复制代码
private Fetch<K, V> fetchRecords(CompletedFetch completedFetch, int maxRecords) {
    // 已省略无关代码。。。
    // 本次从borker拉取的数据
    List<ConsumerRecord<K, V>> partRecords = completedFetch.fetchRecords(maxRecords);
    log.trace("Returning {} fetched records at offset {} for assigned partition {}",
            partRecords.size(), position, completedFetch.partition);
    boolean positionAdvanced = false;
    // position.offset:当前的消费位移  completedFetch.nextFetchOffset:消费过这批数据之后的位移 
    // 例如:当前消费位移position.offset为33,partRecords中有10条数据是本次要消费的,completedFetch.nextFetchOffset为43
    if (completedFetch.nextFetchOffset > position.offset) {
        // 重新构建当前消费者对应主题分区的消费位移信息 
        FetchPosition nextPosition = new FetchPosition(
                completedFetch.nextFetchOffset,
                completedFetch.lastEpoch,
                position.currentLeader);
        log.trace("Updating fetch position from {} to {} for partition {} and returning {} records from `poll()`",
                position, nextPosition, completedFetch.partition, partRecords.size());
        // 重新设置当前消费者对应主题分区的消费位移信息 设置为43
        subscriptions.position(completedFetch.partition, nextPosition);
        positionAdvanced = true;
    }
    return Fetch.forPartition(completedFetch.partition, partRecords, positionAdvanced);
}

在上述代码中,每当消费者拉取到要消费的数据时,会先根据主题分区信息获取消费位移,主题分区与当前消费者的位移信息用LinkedHashMap存储,再次重置当前消费者的消费位移信息。

4、总结

个人名词理解

提交位移:保存在kafka broker中,消费者消费消息进行commit提交到broker之后,更新提交位移,消费者重启后会从上次的提交位移处拉取数据。

消费位移:保存在消费者本地内存中,首次启动会从broker获取提交位移更新为本地的消费位移,后续每次从broker拉取消息后都会更新消费位移(即使一直消费不提交),消费者重启后又随之更新为提交位移。

kafka消费者每次启动时会向broker发送请求同步当前主题分区的提交位移信息offset,并且保存在本地内存,后续再次拉取数据也会携带这个信息,拉取到数据后进行更新。

若有错误,还望批评指正

相关推荐
程序员爱钓鱼1 小时前
Go语言实战案例-创建模型并自动迁移
后端·google·go
javachen__1 小时前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
uzong6 小时前
技术故障复盘模版
后端
GetcharZp7 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程7 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研7 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi8 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国9 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy9 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack9 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt