Sender 线程在发送消息的的sendProducerData()方法中,会对Kafka的的每个node 进行检测是否可以发送消息,将没有就绪的node节点移除,这个时候就会调用NetworkClient的ready方法对指定的node 进行检测。
可用则为true,不可用则返回false,并会初始化一个连接
/**
* Begin connecting to the given node, return true if we are already connected and ready to send to that node.
*
* 检测指定node是否可以发送请求
* 如果可以发送,那么就返回true,否则就初始化一个连接,
* @param node The node to check
* @param now The current timestamp
* @return True if we are ready to send to the given node
*/
@Override
public boolean ready(Node node, long now) {
if (node.isEmpty())
throw new IllegalArgumentException("Cannot connect to empty node " + node);
//是否已经准备好发送请求 [2]
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;
}
是否能够建立连接:
public boolean canConnect(String id, long now) {
//该连接没有建立,或者连接断开,并且距离最近一次的连接时间超过了重试周期reconnect.backoff.max.ms 配置
NodeConnectionState state = nodeState.get(id);
if (state == null)
return true;
else
return state.state.isDisconnected() &&
now - state.lastConnectAttemptMs >= state.reconnectBackoffMs;
}
@Override
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
/**是否我们现在需要更新元数据,如果需要更新数据,该方法返回false,
*
* !metadataUpdater.isUpdateDue(now) 现在不需要更新
*/
// 以及该node是否已经可以发送请求
return !metadataUpdater.isUpdateDue(now) && canSendRequest(node.idString(), now);
}
isReady判断:
当前元数据不需要进行更新,并且可以发送消息则 该node 连接就绪。
public boolean isUpdateDue(long now) {
//没有处于正在更新的过程中,metadata应该更新
return !this.metadataFetchInProgress && this.metadata.timeToNextUpdate(now) == 0;
}
/**
* The next time to update the cluster info is the maximum of the time the current info will expire and the time the
* current info can be updated (i.e. backoff time has elapsed); If an update has been request then the expiry time
* is now
* 计算下次更新metadata时间,这里也就是metadata更新策略:
*
* 1: 当needUpdate=true时,当前时间 - 上一次更新时间 > refreshBackoffMs 进行更新。
* 否则,继续等待到更新周期refreshBackoffMs进行跟新,
*
* 2:如果needUpdate=false时候,检查metadata是否过期,如果过期了。则对更新周期进行判断。
*
* 3: refreshBackoffMs使用的是 retry.backoff.ms配置。默认更新周期100毫秒。
*
* 总结:metadata的更新周期是100ms,过期时间是metadata.max.age.ms,默认60s。
* 如果当needUpdate=true,当更新开关打开,并且与上一次更新时间间隔达到100ms以上进行更新,
* 在更新开关没有打开的情况下,上一次更新是一个成功的操作,该metadata会缓存metadata.max.age.ms,
* 然后数据过期发生更新操作。
*
* timeToNextUpdate(nowMs)的返回值就是还剩多久需要进行更新操作。
*
*/
public synchronized long timeToNextUpdate(long nowMs) {
long timeToExpire = needUpdate ? 0 : Math.max(this.lastSuccessfulRefreshMs + this.metadataExpireMs - nowMs, 0);
long timeToAllowUpdate = this.lastRefreshMs + this.refreshBackoffMs - nowMs;
return Math.max(timeToExpire, timeToAllowUpdate);
}
总结:当前元数据是否需要更新:
1:元数据正在更新过程中,也就是客户端发起了更新元数据请求,但是服务端还没响应,此时需要等待元数据更新,
2:没有处于更新过程中,那么就会判断是否需要更新,需要更新则进行更新,注意更新周期。
3:如果不需要更新,会进进一步检测,数据是否过期,没有超过过期时间则不需要更新。否则也需要进行更新。
/**
* Are we connected and ready and able to send more requests to the given connection?
* 是否与指定的kafka节点已经建立好连接
* @param node The node
* @param now the current timestamp
*/
private boolean canSendRequest(String node, long now) {
//node的状态被标记为就绪, channel is ready 并且,请求队列可以发送请求
return connectionStates.isReady(node, now) && selector.isChannelReady(node)
&& inFlightRequests.canSendMore(node);
}
三个条件:
1:如果在connectionStates中该节点的状态被标记为就绪,
2:该node关联的KafkaChannel 就绪。
3:请求队列inFlightRequests的状态可以发送消息 canSendMore
/**
* Can we send more requests to this node?
*
* @param node Node in question
* @return true iff we have no requests still being sent to the given node
*
* 重点条件:queue.peekFirst().request().completed,
* 即如果发给这个节点的最早的请求还没有发送完成,是不能再往这个节点发送请求的。
*
* 从canSendMore方法中也可以看出:
* 只要没有超过maxInFlightRequestsPerConnection,
一个node可以有多个in-flight request的
*
* maxInFlightRequestsPerConnection --> max.in.flight.requests.per.connection
*
*
*/
public boolean canSendMore(String node) {
Deque<NetworkClient.InFlightRequest> queue = requests.get(node);
return queue == null || queue.isEmpty() ||
(queue.peekFirst().send.completed() && queue.size() < this.maxInFlightRequestsPerConnection);
}
InFlightRequest保存了所有已发送,但还没收到响应的请求。
如果InFlightRequest为空,也就是没有还在等待响应的请求,
InFlightRequest中第一个,也就是最早的那个请求已经完成,并且容量有余,
InFlightRequest的最大最大size 对应的是max.in.flight.requests.per.connection
也就是 配置限制客户端在单个连接上能够发送的未响应请求的个数
InFlightRequest为空,也就是没有还在等待响应的请求,
InFlightRequest中第一个,也就是最早的那个请求已经完成,并且容量有余,
```java
InFlightRequest的最大最大size 对应的是max.in.flight.requests.per.connection
也就是 配置限制客户端在单个连接上能够发送的未响应请求的个数