Kafka源码分析5-sender线程流程初探

欢迎大家关注 github.com/hsfxuebao/j… ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈

书接上文 Kafka源码分析4-元数据更新机制,本篇文章重点分析sender线程发送消息流程初探。

Kafka源码分析3-Producer核心流程分析 中并没有详细分析sender线程,代码如下

void run(long now) {

    /** 获取元数据
     *    因为我们是根据场景驱动的方式,目前是我们第一次代码进来,
     *    目前还没有获取到元数据
     *    所以这个cluster里面是没有元数据
     *    如果这儿没有元数据的话,这个方法里面接下来的代码就不用看了
     *    是因为接下来的这些代码依赖这个元数据。
     *    TODO 我们直接看这个方法的最后一行代码
     *    就是这行代码去拉取的元数据。
     */
    /**
     * 我们用场景驱动的方式,现在我们的代码是第二次进来
     * 第二次进来的时候,已经有元数据了,所以cluster这儿是有元数据。
     * 步骤一:
     *      获取元数据
     */
    Cluster cluster = metadata.fetch();
    // get the list of partitions with data ready to send
    /**
     * 步骤二:
     *      首先是判断哪些partition有消息可以发送,获取到这个partition的leader partition对应的broker主机。
     *      哪些broker上面需要我们去发送消息?
     */
    RecordAccumulator.ReadyCheckResult result = this.accumulator.ready(cluster, now);

    /**
     * 步骤三:
     *      标识还没有拉取到元数据的topic
     */
    // if there are any partitions whose leaders are not known yet, force metadata update
    if (!result.unknownLeaderTopics.isEmpty()) {
        // The set of topics with unknown leader contains topics with leader election pending as well as
        // topics which may have expired. Add the topic again to metadata to ensure it is included
        // and request metadata update, since there are messages to send to the topic.
        for (String topic : result.unknownLeaderTopics)
            this.metadata.add(topic);
        this.metadata.requestUpdate();
    }

    // remove any nodes we aren't ready to send to
    Iterator<Node> iter = result.readyNodes.iterator();
    long notReadyTimeout = Long.MAX_VALUE;
    while (iter.hasNext()) {
        Node node = iter.next();
        /**
         * 步骤四:检查与要发送数据的主机的网络是否已经建立好。
         */
        if (!this.client.ready(node, now)) {
            //如果返回的是false  !false 代码就进来
            //移除result 里面要发送消息的主机。
            //所以我们会看到这儿所有的主机都会被移除
            iter.remove();
            notReadyTimeout = Math.min(notReadyTimeout, this.client.connectionDelay(node, now));
        }
    }

    // create produce requests
    /**
     * 步骤五:
     *
     * 我们有可能要发送的partition有很多个,
     * 很有可能有一些partition的leader partition是在同一台服务器上面。
     * p0:leader:0
     * p1:leader: 0
     * p2:leader: 1
     * p3:leader: 2
     *      假设我们集群只有3台服务器
     * 当我们的分区的个数大于集群的节点的个数的时候,一定会有多个leader partition在同一台服务器上面。
     *
     * 按照broker进行分组,同一个broker的partition为同一组
     * 0:{p0,p1}
     * 1:{p2}
     * 2:{p3}
     */
    // 如果网络没有建立好,这的代码是不执行的
    Map<Integer, List<RecordBatch>> batches = this.accumulator.drain(cluster,
                                                                     result.readyNodes,
                                                                     this.maxRequestSize,
                                                                     now);
    if (guaranteeMessageOrder) {
        // Mute all the partitions drained
        for (List<RecordBatch> batchList : batches.values()) {
            //如果batches 空的话,这而的代码也就不执行了。
            for (RecordBatch batch : batchList)
                this.accumulator.mutePartition(batch.topicPartition);
        }
    }

    /**
     * 步骤六:
     *  对超时的批次是如何处理的?
     */
    List<RecordBatch> expiredBatches = this.accumulator.abortExpiredBatches(this.requestTimeout, now);
    // update sensors
    for (RecordBatch expiredBatch : expiredBatches)
        this.sensors.recordErrors(expiredBatch.topicPartition.topic(), expiredBatch.recordCount);

    sensors.updateProduceRequestMetrics(batches);

    // If we have any nodes that are ready to send + have sendable data, poll with 0 timeout so this can immediately
    // loop and try sending more data. Otherwise, the timeout is determined by nodes that have partitions with data
    // that isn't yet sendable (e.g. lingering, backing off). Note that this specifically does not include nodes
    // with sendable data that aren't ready to send since they would cause busy looping.
    long pollTimeout = Math.min(result.nextReadyCheckDelayMs, notReadyTimeout);
    if (!result.readyNodes.isEmpty()) {
        log.trace("Nodes with data ready to send: {}", result.readyNodes);
        pollTimeout = 0;
    }

    /**
     * 步骤七:
     *      创建发送消息的请求
     * 创建请求
     * 我们往partition上面去发送消息的时候,有一些partition他们在同一台服务器上面
     * ,如果我们一分区一个分区的发送我们网络请求,那网络请求就会有一些频繁
     * 我们要知道,我们集群里面网络资源是非常珍贵的。
     * 会把发往同个broker上面partition的数据 组合成为一个请求。
     * 然后统一一次发送过去,这样子就减少了网络请求。
     */
    //如果网络连接没有建立好 batches其实是为空。
    //也就说其实这段代码也是不会执行。
    sendProduceRequests(batches, now);

    // if some partitions are already ready to be sent, the select time would be 0;
    // otherwise if some partition already has some data accumulated but not ready yet,
    // the select time will be the time difference between now and its linger expiry time;
    // otherwise the select time will be the time difference between now and the metadata expiry time;
    /**
     * 步骤八:
     * 真正执行网络操作的都是这个NetWorkClient这个组件
     * 包括:发送请求,接受响应(处理响应)
     */
    // 我们猜这儿可能就是去建立连接。
    this.client.poll(pollTimeout, now);
}
复制代码

总结

本篇文章不打算对其中的步骤进行详细的分析,后面我们将重点分析其中的几个步骤。

参考文档:

史上最详细kafka源码注释(kafka-0.10.2.0-src)

kafka技术内幕-图文详解Kafka源码设计与实现

Kafka 源码分析系列

猜你喜欢

转载自juejin.im/post/7018524187715174430