MetadataUpdater是个接口类,DefaultMetadataUpdater是它的默认实现。主要包括字段有:
class DefaultMetadataUpdater implements MetadataUpdater {
/* 记录集群元数据 */
private final Metadata metadata;
/* 标记是否已经发送了metadata请求,如果已经发送了就不用重复发送。 */
private boolean metadataFetchInProgress;
/* 当检测到没有可用的节点时,用此字段来记录时间戳 */
private long lastNoNodeAvailableMs;
}
maybeUpdate方法是DefaultMetadataUpdater的核心方法,用来判断metadata中的元数据是否需要更新。如果metadataFetchInProgress为false,则下列条件之一即可更新:
1. Metadata.needUpdate字段为true
2. 长时间没有更新,默认5分钟一次
Metadata 的强制更新会在以下几种情况下进行:
1. initConnect 方法调用时,初始化连接;
2. poll() 方法中对 handleDisconnections() 方法调用来处理连接断开的情况,这时会触发强制更新;
3. poll() 方法中对 handleTimedOutRequests() 来处理请求超时时;
4. 发送消息时,如果无法找到 partition 的 leader;
5. 处理 Producer 响应(handleProduceResponse),如果返回关于 Metadata 过期的异常,比如:没有 topic-partition 的相关 meta 或者 client 没有权限获取其 metadata。
在metadata请求发送之前,metadataFetchInProgress设置为true,然后从node中选择负载最小的Node节点,根据InFlightRequests判断。然后和发送消息一样,设置到KafkaChannel的send字段,通过KSelector.poll()方法发送。
class DefaultMetadataUpdater implements MetadataUpdater {
public long maybeUpdate(long now) {
// 得到一个下次更新metadata的时间戳。
long timeToNextMetadataUpdate = metadata.timeToNextUpdate(now);
// 下次尝试连接服务器的时间戳
long timeToNextReconnectAttempt = Math.max(this.lastNoNodeAvailableMs + metadata.refreshBackoff() - now, 0);
// 是否已经发送了metadata请求
long waitForMetadataFetch = this.metadataFetchInProgress ? Integer.MAX_VALUE : 0;
// 下次可以发送metadata请求的时间差
long metadataTimeout = Math.max(Math.max(timeToNextMetadataUpdate, timeToNextReconnectAttempt),
waitForMetadataFetch);
// 允许发送
if (metadataTimeout == 0) {
// 找到负载最小的node节点
Node node = leastLoadedNode(now);
maybeUpdate(now, node);
}
return metadataTimeout;
}
private void maybeUpdate(long now, Node node) {
//检测node是否可用。
if (node == null) {
log.debug("Give up sending metadata request since no node is available");
// 记录无节点可用时的时间戳
this.lastNoNodeAvailableMs = now;
return;
}
String nodeConnectionId = node.idString();
/* 是否允许向node发送请求。
private boolean canSendRequest(String node) {
return connectionStates.isConnected(node) && selector.isChannelReady(node) && inFlightRequests.canSendMore(node);
}
*/
if (canSendRequest(nodeConnectionId)) {
this.metadataFetchInProgress = true;
MetadataRequest metadataRequest;
//指定需要更新元数据的topic
if (metadata.needMetadataForAllTopics())
metadataRequest = MetadataRequest.allTopics();
else
metadataRequest = new MetadataRequest(new ArrayList<>(metadata.topics()));
// 封装成ClientRequest
ClientRequest clientRequest = request(now, nodeConnectionId, metadataRequest);
log.debug("Sending metadata request {} to node {}", metadataRequest, node.id());
//缓存请求,在下次poll操作中发送出去
doSend(clientRequest, now);
} else if (connectionStates.canConnect(nodeConnectionId, now)) {
// we don't have a connection to this node right now, make one
log.debug("Initialize connection to node {} for sending metadata request", node.id());
// 如果没有连接这个 node,那就初始化连接
initiateConnect(node, now);
// If initiateConnect failed immediately, this node will be put into blackout and we
// should allow immediately retrying in case there is another candidate node. If it
// is still connecting, the worst case is that we end up setting a longer timeout
// on the next round and then wait for the response.
} else { // connected, but can't send more OR connecting
// In either case, we just need to wait for a network event to let us know the selected
// connection might be usable again.
// 如果 client 正在与任何一个 node 的连接状态是 connecting,那么就进行等待
this.lastNoNodeAvailableMs = now;
}
}
}
所以,每次 Producer 请求更新 metadata 时,会有以下几种情况:
1. 如果 node 可以发送请求,则直接发送请求;
2. 如果该 node 正在建立连接,则直接返回;
3. 如果该 node 还没建立连接,则向 broker 初始化链接。
而 KafkaProducer 线程之前是一直阻塞在两个 while 循环中,直到 metadata 更新
1. sender 线程第一次调用 poll() 方法时,初始化与 node 的连接;
2. sender 线程第二次调用 poll() 方法时,发送 Metadata 请求;
3. sender 线程第三次调用 poll() 方法时,获取 metadataResponse,并更新 metadata。
经过上述 sender 线程三次调用 poll()方法,所请求的 metadata 信息才会得到更新,此时 Producer 线程也不会再阻塞,开始发送消息。
在收到了metadataResponse之后,先调用maybeHandleCompletedReceive判断是否为metadataResponse(根据APIKey判断)。是则调用handleResponse,更新cluster对象。
class DefaultMetadataUpdater implements MetadataUpdater {
private void handleResponse(RequestHeader header, Struct body, long now) {
//修改metadataFetchInProgress
this.metadataFetchInProgress = false;
MetadataResponse response = new MetadataResponse(body);
//创建cluster对象
Cluster cluster = response.cluster();
// check if any topics metadata failed to get updated
Map<String, Errors> errors = response.errors();
if (!errors.isEmpty())
log.warn("Error while fetching metadata with correlation id {} : {}", header.correlationId(), errors);
// don't update the cluster if there are no valid nodes...the topic we want may still be in the process of being
// created which means we will get errors and no nodes until it exists
if (cluster.nodes().size() > 0) {
// 统治metadata上的监听器,更新cluster字段,最后唤醒等待metadata更新完成的线程。
this.metadata.update(cluster, now);
} else {
log.trace("Ignoring empty metadata response with correlation id {}.", header.correlationId());
this.metadata.failedUpdate(now);
}
}
}
当连接断开或者其他异常无法获取响应时,maybeHandleDisconnection处理。
class DefaultMetadataUpdater implements MetadataUpdater {
public boolean maybeHandleDisconnection(ClientRequest request) {
ApiKeys requestKey = ApiKeys.forId(request.request().header().apiKey());
if (requestKey == ApiKeys.METADATA) {
Cluster cluster = metadata.fetch();
if (cluster.isBootstrapConfigured()) {
int nodeId = Integer.parseInt(request.request().destination());
Node node = cluster.nodeById(nodeId);
// 输出节点日志信息
if (node != null)
log.warn("Bootstrap broker {}:{} disconnected", node.host(), node.port());
}
// 保证下次发送
metadataFetchInProgress = false;
return true;
}
return false;
}
}
部分参考https://www.jianshu.com/p/bb7c332eac25