Kafka生产者介绍(十):MetadataUpdater

本文介绍了MetadataUpdater接口类及其默认实现DefaultMetadataUpdater。阐述了maybeUpdate方法判断元数据更新条件,以及Metadata强制更新的几种情况。还说明了metadata请求发送流程、Producer请求更新metadata的不同情况,以及sender线程三次调用poll()方法更新metadata,最后提及响应处理和异常处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值