源码分析之Kafka Consumer消费消息的过程

源码分析之Kafka Consumer消费消息的过程文章目录源码分析之Kafka Consumer消费消息的过程说明Consumer如何使用?代码示例主要流程订阅主题的过程是如何实现的?经典思路:主动检测不支持的情况并抛出异常,避免系统产生不可预期的行为有关元数据更新拉取消息的过程是如何实现的?updateAssignmentMetadataIfNeeded() 更新元数据Coordinator#poll() 维持心跳,更新元数据ConsumerNetworkClient#poll() 封装所有网络通信
摘要由CSDN通过智能技术生成

说明

本文基于Apache Kafka 2.5.1(2020.09.10拉取最新代码)

Consumer如何使用?

阅读源码前的首先要做到熟悉相关组件的概念、基本使用。而最靠谱的资料就是官方文档。

建议阅读官方文档(https://kafka.apache.org/documentation/)后,自己练习、使用kafka之后再开始阅读源码。

KafkaConsumer的JavaDoc(参见https://kafka.apache.org/10/javadoc/?org/apache/kafka/clients/consumer/KafkaConsumer.html)本身就给出了不少有用信息,下面仅列出一些关键点:

  • Cross-Version Compatibility
    客户端支持0.10.0以及以上版本,如果调用不支持的特性,会抛出UnsupportedVersionException

  • Offsets and Consumer Position
    position: 有待读取的下一条记录的偏移量
    commited position: 已被保存、归档的最后一条记录的偏移量,可以用于恢复数据。

  • Consumer Groups and Topic Subscriptions

    • 一个partition内的消息只会投递给group中的一个consumer
    • 以下场景将触发group rebalance
      • 一个consumer挂掉
      • 新加入一个consumer
      • 新的partition加入一个topic
      • 一个新的topic匹配已有的订阅正则(subscribed regx)
    • ConsumerRebalanceListener 可以监听rebalance
  • Detecting Consumer Faillures

    • 调用poll(long)方法时consumer会自动加入到group中,consumer会发心跳给服务器端,超时时间session.timeout.ms
    • consumer可能遇到活锁:锁检测机制 session.timeout.ms
    • poll方法相关配置:
      • max.poll.interval.ms
      • max.poll.records

KafkaConsumer源码中给出的代码示例

//代码1:
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "1000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("foo", "bar"));
while (true) {
   
    ConsumerRecords<String, String> records = consumer.poll(100);
    for (ConsumerRecord<String, String> record : records)
        System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
上述示例主要流程
  1. 设置配置信息,包括broker地址,consumer group id, 自动提交消费的位置,序列化配置
  2. 创建KafkaConsumer对象
  3. 订阅2个topic: foo, bar
  4. 循环拉取并打印

以下将重点解读KafkaConsumer消费流程中的3个问题:

  1. 订阅主题的过程是如何实现的?
  2. consumer如何与coordinator协商,确定消费哪些partition?
  3. 拉取消息的过程是如何实现的?

订阅主题的过程是如何实现的?

仍以上面代码示例为例,跟踪源码中的subscribe方法,最终会看到KafkaConsumer中的如下代码:

//代码2:
@Override
public void subscribe(Collection<String> topics, ConsumerRebalanceListener listener) {
   
    acquireAndEnsureOpen();
    try {
   
        maybeThrowInvalidGroupIdException();
        if (topics == null)
            throw new IllegalArgumentException("Topic collection to subscribe to cannot be null");
        if (topics.isEmpty()) {
   
            // treat subscribing to empty topic list as the same as unsubscribing
            this.unsubscribe();
        } else {
   
            for (String topic : topics) {
   
                if (topic == null || topic.trim().isEmpty())
                    throw new IllegalArgumentException("Topic collection to subscribe to cannot contain null or empty topic");
            }

            throwIfNoAssignorsConfigured();
            fetcher.clearBufferedDataForUnassignedTopics(topics);
            log.info("Subscribed to topic(s): {}", Utils.join(topics, ", "));
            if (this.subscriptions.subscribe(new HashSet<>(topics), listener))
            	//元数据更新
                metadata.requestUpdateForNewTopics();
        }
    } finally {
   
        release();
    }
}

基本流程如下:

  1. 加轻量级锁(通过CAS方式加锁)
  2. 参数校验
    1. 注意:如果传入topic集合为空,则直接走unsubscribe的逻辑
  3. 重置订阅状态subscriptions,更新元数据metadata中的topic信息
    1. 订阅状态维护:订阅的 topic 和 patition 的消费位置等状态信息
    2. 元数据metada维护:Kafka 集群元数据的一个子集,包括集群的 Broker 节点、Topic 和 Partition 在节点上分布,Coordinator 给 Consumer 分配的 Partition 信息。

经典思路:主动检测不支持的情况并抛出异常,避免系统产生不可预期的行为

KafkaConsumer的Javadoc明确声明了,consumer不是线程安全的,被并发调用时会出现不可预期的结果。为了避免这种情况发生,Kafka 做了主动的检测并抛出异常,而不是放任系统产生不可预期的情况。

Kafka“主动检测不支持的情况并抛出异常,避免系统产生不可预期的行为”这种模式,对于增强的系统的健壮性是一种非常有效的做法。如果你的系统不支持用户的某种操作,正确的做法是,检测不支持的操作,直接拒绝用户操作,并给出明确的错误提示,而不应该只是在文档中写上“不要这样做”,却放任用户错误的操作,产生一些不可预期的、奇怪的错误结果。

引自:极客时间:消息队列高手课 李玥

具体代码就是上面的代码2中的acquireAndEnsureOpen(),该方法的代码实现如下:

/**
     * Acquire the light lock and ensure that the consumer hasn't been closed.
     * @throws IllegalStateException If the consumer has been closed
     */
private void acquireAndEnsureOpen() {
   
    acquire();
    //KafkaConsumer成员变量,初始值为false,调用close(Duration)方法后才会置为true
    if (this.closed) {
   
        release();
        throw new IllegalStateException("This consumer has already been closed.");
    }
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值