欢迎大家关注 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));
}
}
NetworkClient 的ready方法:
@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源码设计与实现