Kafka源码分析7-筛选可以发送消息的broker

748 阅读5分钟

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

Kafka源码分析5-sender线程流程初探 已经分析了sender线程的整体流程,本文重点分析步骤四,检查与要发送数据的主机的网络是否已经建立好。sender 线程代码:

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));
    }
}

NetworkClientready方法:

@Override
public boolean ready(Node node, long now) {
    if (node.isEmpty())
        throw new IllegalArgumentException("Cannot connect to empty node " + node);
    //判断要发送消息的主机,是否具备发送消息的条件
    if (isReady(node, now))
        return true;
    //判断是否可以尝试去建立网络
    if (connectionStates.canConnect(node.idString(), now))
        // if we are interested in sending to a node and we don't have a connection to it, initiate one
        initiateConnect(node, now);

    return false;
}

首先我们来看一下isRead() 判断要发送消息的主机是否具备好发送消息的条件,

public boolean isReady(Node node, long now) {
    // if we need to update our metadata now declare all requests unready to make metadata requests first
    // priority
    //我们要发送写数据请求的时候,不能是正在更新元数据的时候。
    return !metadataUpdater.isUpdateDue(now) && canSendRequest(node.idString());
}
我们先来看isUpdateDue() 方法:
public boolean isUpdateDue(long now) {
    return !this.metadataFetchInProgress && this.metadata.timeToNextUpdate(now) == 0;
}
public synchronized long timeToNextUpdate(long nowMs) {
    /**
     * 元数据是否过期,判断条件:
     * 1. needUpdate被置为true
     * 2. 上次更新时间距离当前时间已经超过了指定的元数据过期时间阈值metadataExpireMs(metadata.max.age.ms),默认是300秒
     */
    long timeToExpire = needUpdate ? 0 : Math.max(this.lastSuccessfulRefreshMs + this.metadataExpireMs - nowMs, 0);
    /**
     * 允许更新的时间点,计算方式:
     * 上次更新时间 + 退避时间 - 当前时间的间隔
     * 即要求上次更新时间与当前时间的间隔不能大于退避时间,如果大于则需要等待
     */
    long timeToAllowUpdate = this.lastRefreshMs + this.refreshBackoffMs - nowMs;
    return Math.max(timeToExpire, timeToAllowUpdate);
}
其次,我们再来看canSendRequest()方法:
private boolean canSendRequest(String node) {

   /**
    * connectionStates.isReady(node):
    *  生产者:多个连接,缓存多个连接(跟我们的broker的节点数是一样的)
    *     判断缓存里面是否已经把这个连接给建立好了。
    * selector.isChannelReady(node):
    *      java NIO:selector
    *      selector -> 绑定了多个KafkaChannel(java socketChannel)
    *      一个kafkaChannel就代表一个连接。
    *
    *  inFlightRequests.canSendMore(node):
    *  每个往broker主机上面发送消息的连接,最多能容忍5个消息,发送出去了但是还没有接受到响应。
    *  发送数据的顺序。
    *  1,2,3,4,5
    *  有可能到生产者的顺序如下:
    *  2,3,4,5,1
    */
   return connectionStates.isReady(node) && selector.isChannelReady(node) && inFlightRequests.canSendMore(node);
}

判断要发送消息的主机,具备发送消息的条件,必须同时满足下面的两个条件:

  • !metadataUpdater.isUpdateDue(now) 当前不能处于元数据加载的过程,而且下一次要更新元数据的间隔时间为0,现在没有加载元数据,但是马上就应该要加载元数据了,如果对上述条件判断是非的话,要不然是正在加载元数据,或者是还没到加载元数据的时候。为什么一定要有这个条件?假设此时必须要更新元数据了,就不能发送请求,必须要等待这个元数据被刷新了再次去发送请求

  • canSendRequest(node.idString())中的三个条件:

  • connectionStates.isReady(node)有一个Broker连接状态的缓存,先查一下这个缓存,当前这个Broker是否已经建立了连接了,如果是的话,才可以继续判断其他的条件

  • selector.isChannelReady(node) Selector,你大概可以认为底层封装的就是Java NIO的 Selector,Selector上要注册很多Channel,每个Channel就代表了跟一个Broker建立的连接

  • inFlightRequests.canSendMore(node) inFlightRequests,有一个参数可以设置这个东西,默认是对同一个Broker同一时间最多容忍5个请求发送过去但是还没有收到响应,所以如果对一个Broker已经发送了5个请求,都没收到响应,此时就不可以继续发送了

再多说一下inFlightRequests 的参数,如果单个分区保证顺序性,这个参数必须设置成1。

但是如果上述条件不满足,假设是因为还没有建立连接,此时如何判断是否可以跟一个Broker建立连接呢?先看 connectionStates.canConnect(node.idString(), now) 代码:

public boolean canConnect(String id, long now) {
    //首先从缓存里面获取当前主机的连接。
    NodeConnectionState state = nodeState.get(id);
    if (state == null)
        return true;
    else
        //可以从缓存里面获取到连接。
        //但是连接的状态是DISCONNECTED 并且
        // now - state.lastConnectAttemptMs >= this.reconnectBackoffMs 说明可以进行重试,重试连接。
        return state.state == ConnectionState.DISCONNECTED && now - state.lastConnectAttemptMs >= this.reconnectBackoffMs;
}

先找到broker id对应的一个连接状态,如果此时这个连接状态是null,就说明之前从来没有建立过连接,此时就可以直接返回true,就说明可以跟这个broker建立连接;否则如果连接状态已经存在,如果当前broker的状态是断开连接,而且上一次跟这个broker尝试建立连接的时间到现在,已经超过了重试的时间了,默认100ms

如果 connectionStates.canConnect(node.idString(), now) 返回true,接下来调用建立网络连接的方法initiateConnect(node, now) ,这块我们下篇文章进行详细分析。

参考文档:

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

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

Kafka 源码分析系列