SpringKafka自动提交源码学习-Spring生态(二)

本文详细解读了SpringKafka中自动提交偏移量的底层实现,涉及KafkaConsumer.poll、ConsumerCoordinator和fetcher的交互过程。通过源码跟踪,揭示了自动提交是基于心跳机制异步进行的,每5秒更新消费者位置信息并主动请求更新。

Spring Kafka 可以为我们提供非常简单易用的的上层 API 支持. 在一般处理无需幂等的数据场景下, 我们可以使用默认配置 enable-auto-commit 来使用消息队列. 这里有个疑问, auto-commit 究竟是怎么实现的, 具体在怎样的场景中适用 auto-commit 配置? 带着这些问题, 本节基于 spring-kafka2.7.0 跟踪源码, 观察自动提交的底层实现.

依赖

Spring Kafka 是对 kafka-client 的再封装, 这里列一下, 本文使用的 jar 包版本

  • spring-kafka2.7.0
  • kafka-client2.6.0

结论

为了方便大家往后理解源码实现, 先说结论.
spring-kafka 中并没有实现自动提交的相关功能, 它只是将 ‘enable-auto-commit=true’ 这个参数交给了 kafka-client. kafka-client 消费者每次成功从 Topic 拉取 (poll) 数据后, 都会递增更新消费者偏移量. kafka-client 中有个 SubscriptionState 的类, 专门存储当前消费者监听的 topics、partition、offset 信息.
消费者 poll 消息前, 会触发对 GroupCoordinator 心跳机制校验, 在缺省配置中, kafka-client 每 5 秒会从 SubscriptionState 中获取当前消费者的所有 topics、partition、offset 信息, 并主动发起异步更新的请求.

自动提交架构

源码跟踪

SpringKafka源码doPoll

KafkaMessageListenerContainer.doPoll

前置章节介绍了 spring-kafka 的结构, 我们知道 KafkaMessageListenerContainer 与 kafka-client 实例是一一对应的关系. 并且 KafkaMessageListenerContainer.doPoll() 这个方法是 consumer 获取消息 (Message) 的入口.
在 doPoll() 中, 是调用 this.consumer.poll(this.pollTimeout) 主动拉取数据

@Nullable
private ConsumerRecords<K, V> doPoll() {
   
   
	ConsumerRecords<K, V> records;
	if (this.isBatchListener && this.subBatchPerPartition) {
   
   
		if (this.batchIterator == null) {
   
   
			this.lastBatch = this.consumer.poll(this.pollTimeout);
			if (this.lastBatch.count() == 0) {
   
   
				return this.lastBatch;
			}
			else {
   
   
				this.batchIterator = this.lastBatch.partitions().iterator();
			}
		}
		TopicPartition next = this.batchIterator.next();
		List<ConsumerRecord<K, V>> subBatch = this.lastBatch.records(next);
		records = new ConsumerRecords<>(Collections.singletonMap(next, subBatch));
		if (!this.batchIterator.hasNext()) {
   
   
			this.batchIterator = null;
		}
	}
	else {
   
   
		records = this.consumer.poll(this.pollTimeout);
		checkRebalanceCommits();
	}
	return records;
}

前置章节内容请优先阅读
SpringKafka原理解析及源码学习-Spring生态(一): http://blog.diswares.cn/kafka-spring-kafka-structure/

KafkaConsumer.poll

  • 这个方法中可以看到一个 do while 的结构, while 中的判断我们可以知道, 当一次 poll 超过一定时间还是没有拉取到数据, 会认为本次拉取失败从而不继续 poll
  • do while 里的代码块, 有两个比较关键的方法: updateAssignmentMetadataIfNeeded(timer, false) 和 pollForFetches(timer)
  • updateAssignmentMetadataIfNeeded(timer, false): 此方法为自动提交偏移量的入口, 也是更新 fetcher 信息的入口
  • pollForFetches(timer): 拉取新数据
@Override
public ConsumerRecords<K, V> poll(final Duration timeout) {
   
   
    return poll(time.timer(timeout), true);
}

private ConsumerRecords<K, V> poll(final Timer timer, final boolean includeMetadataInTimeout) {
   
   
    acquireAndEnsureOpen();
    try {
   
   
        // 记录一下 本次 poll 的时间信息
        this.kafkaConsumerMetrics.recordPollStart(timer.currentTimeMs());

        if (this.subscriptions.hasNoSubscriptionOrUserAssignment()) {
   
   
            throw new IllegalStateException("Consumer is not subscribed to any topics or assigned any partitions");
        }

        // do while 直至超时
        do {
   
   
            client.maybeTriggerWakeup();

            if (includeMetadataInTimeout) {
   
   
                // 更新 Fetcher 的元信息并自动提交 offset
                updateAssignmentMetadataIfNeeded(timer, false);
            } else {
   
   
                while (!updateAssignmentMetadataIfNeeded(time.timer(Long.MAX_VALUE), true)) {
   
   
                    log.warn("Still waiting for metadata");
                }
            }

            // nio 拉取新消息
            final Map<TopicPartition, List<ConsumerRecord<K, V>>> records = pollForFetches(timer);
            if (!records.isEmpty()) {
   
   
                if (fetcher.sendFetches() > 0 || client.hasPendingRequests()) {
   
   
                    client.transmitSends();
                }

                return this.interceptors.onConsume(new ConsumerRecords<>(records));
            }
            // 超时校验
        } while (timer.notExpired());

        return ConsumerRecords.empty();
    } finally {
   
   
        release();
        this.kafkaConsumerMetrics.recordPollEnd(timer.currentTimeMs()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值