2.1.2 客户端消息发送线程


2.1.2 客户端消息发送线程

追加消息到记录收集器时按照分区进行分组,并放到 batches集合中,每个分区的队列都保存了即将发送到这个分区对应节点上的批记录,客户端的发送线程可 以只使用一个 Sender线程迭代batches的每个分区,获取分区对应的主剧本节点,取出分区对应的队列中的批记录就可以发送消息了 。

消息发送线程有两种消息发送方式:按照分区直接发送 、 按照分区的目标节点发迭。 假设有两台服务器, 主题有6个分区,那么每台服务器就有3个分区。 如图2-7 (左)所示,消息发送线程迭代batches的每个分区,直接往分区的主副本节点发送消息,总共会有6个请求。如图2-7 (右)所示,我们先按照分区的主副本节点进行分组,把属于同一个节点的所有分区放在一起,总共只有两个请求。第二种做法可以大大减少网络的开销。

在这里插入图片描述

  1. 从记录收集器获取数据
    生产者发送的消息在客户端首先被保存到记录收集器中,发送线程需要发送消息时,从中获取就可以了。不过记录收集器并不仅仅将消息暂存起来, 而且为了使发送线程能够更好地工作,追加到记录收集器的消息将按照分区放好。在发送线程需要数据时,记录收集器能够按照节点将消息重新分组再交给发送线程。发送线程从记录收集器中得到每个节点上需要发送的批记录列表 , 为每个节点都创建一个客户端请求(ClientRequest )。相关代码如下 :
private long sendProducerData(long now) {
        Cluster cluster = metadata.fetch();
        // get the list of partitions with data ready to send
        RecordAccumulator.ReadyCheckResult result = this.accumulator.ready(cluster, now);

        // if there are any partitions whose leaders are not known yet, force metadata update
        if (!result.unknownLeaderTopics.isEmpty()) {
            // The set of topics with unknown leader contains topics with leader election pending as well as
            // topics which may have expired. Add the topic again to metadata to ensure it is included
            // and request metadata update, since there are messages to send to the topic.
            for (String topic : result.unknownLeaderTopics)
                this.metadata.add(topic);
            this.metadata.requestUpdate();
        }

        // remove any nodes we aren't ready to send to
        Iterator<Node> iter = result.readyNodes.iterator();
        long notReadyTimeout = Long.MAX_VALUE;
        while (iter.hasNext()) {
            Node node = iter.next();
            if (!this.client.ready(node, now)) {
                iter.remove();
                notReadyTimeout = Math.min(notReadyTimeout, this.client.connectionDelay(node, now));
            }
        }

        // create produce requests
        Map<Integer, List<ProducerBatch>> batches = this.accumulator.drain(cluster, result.readyNodes,
                this.maxRequestSize, now);
        if (guaranteeMessageOrder) {
            // Mute all the partitions drained
            for (List<ProducerBatch> batchList : batches.values()) {
                for (ProducerBatch batch : batchList)
                    this.accumulator.mutePartition(batch.topicPartition);
            }
        }

        List<ProducerBatch> expiredBatches = this.accumulator.expiredBatches(this.requestTimeout, now);
        boolean needsTransactionStateReset = false;
        // Reset the producer id if an expired batch has previously been sent to the broker. Also update the metrics
        // for expired batches. see the documentation of @TransactionState.resetProducerId to understand why
        // we need to reset the producer id here.
        if (!expiredBatches.isEmpty())
            log.trace("Expired {} batches in accumulator", expiredBatches.size());
        for (ProducerBatch expiredBatch : expiredBatches) {
            failBatch(expiredBatch, -1, NO_TIMESTAMP, expiredBatch.timeoutException());
            if (transactionManager != null && expiredBatch.inRetry()) {
                needsTransactionStateReset = true;
            }
            this.sensors.recordErrors(expiredBatch.topicPartition.topic(), expiredBatch.recordCount);
        }

        if (needsTransactionStateReset) {
            transactionManager.resetProducerId();
            return 0;
        }

        sensors.updateProduceRequestMetrics(batches);

        // If we have any nodes that are ready to send + have sendable data, poll with 0 timeout so this can immediately
        // loop and try sending more data. Otherwise, the timeout is determined by nodes that have partitions with data
        // that isn't yet sendable (e.g. lingering, backing off). Note that this specifically does not include nodes
        // with sendable data that aren't ready to send since they would cause busy looping.
        long pollTimeout = Math.min(result.nextReadyCheckDelayMs, notReadyTimeout);
        if (!result.readyNodes.isEmpty()) {
            log.trace("Nodes with data ready to send: {}", result.readyNodes);
            // if some partitions are already ready to be sent, the select time would be 0;
            // otherwise if some partition already has some data accumulated but not ready yet,
            // the select time will be the time difference between now and its linger expiry time;
            // otherwise the select time will be the time difference between now and the metadata expiry time;
            pollTimeout = 0;
        }
        sendProduceRequests(batches, now);
        return pollTimeout;
    }

追加消息到记录收集器的数据结构是batches : TopicPartition → Deque<RecordBatch>,读取记录收集器的数据结构是 batches:Nodeld → Li.st<RecordBatch>。 为了区分这两个 batches,把后者叫作
batches, 从batches转变为batches的步骤如下 。

  1. 迭代 batches 的每个分区,获取TopicPartition应的主副本节点 : NodeId 。
  2. 获取分区的批记录队列中的第一个批记录 : TopicPartition → RecordBatch 。
  3. 将相同主副本节点的所有分区放在一起 : NodeId → List。
  4. 将相同主副本节点的所有批记录放在一起: NodeId → List< RecordBatch> 。

如图2 -8所示,步骤( 1 )产生batches ,步骤(4)产生batches ’ 。 发送线程从记录收集器获取数据,然
后创建客户端请求并发送给服务端,具体步骤如下 。

  1. 消息被记录收集器收集,并按照分区追加到队列的最后一个批记录中 。
  2. 发送钱程通过ready()从记录收集器中找出已经准备好的服务端节点 。
  3. 节点已经准备好 , 如果客户端还没有和 它们建立连接,通过 connect()建立到服务端的连接 。
  4. 发送钱程通过drain()从记录收集器获取按照节点整理好的每个分区的批记录 。
  5. 发送线程得到每个节点的批记录后 , 为每个节点创建客户端请求,并将请求发送到服务端 。

在这里插入图片描述
发送线程不仅要从记录收集器读取数据 , 而且还要将读取到的数据用来创建客户端请求。

  1. 创建生产者客户端请求
    从记录收集器获取出来的 batches 已经按照节点分组了 , 发送线程的produceRequest()方法会为每个节点都创建一个客户端请求 。 ProduceRequest ()方法的 batches参数是指定目标节点的批记录列表。由于一个目标节点就有多个分区,每个批记录都对应了一个分区 。 创建客户端请求时, 批记录列表需要转成分区到字节缓冲区的字典结构。 相关代码如下:
private void sendProduceRequest(long now, int destination, short acks, int timeout, List<ProducerBatch> batches) {
        if (batches.isEmpty())
            return;

        Map<TopicPartition, MemoryRecords> produceRecordsByPartition = new HashMap<>(batches.size());
        final Map<TopicPartition, ProducerBatch> recordsByPartition = new HashMap<>(batches.size());

        // find the minimum magic version used when creating the record sets
        byte minUsedMagic = apiVersions.maxUsableProduceMagic();
        for (ProducerBatch batch : batches) {
            if (batch.magic() < minUsedMagic)
                minUsedMagic = batch.magic();
        }

        for (ProducerBatch batch : batches) {
            TopicPartition tp = batch.topicPartition;
            MemoryRecords records = batch.records();

            // down convert if necessary to the minimum magic used. In general, there can be a delay between the time
            // that the producer starts building the batch and the time that we send the request, and we may have
            // chosen the message format based on out-dated metadata. In the worst case, we optimistically chose to use
            // the new message format, but found that the broker didn't support it, so we need to down-convert on the
            // client before sending. This is intended to handle edge cases around cluster upgrades where brokers may
            // not all support the same message format version. For example, if a partition migrates from a broker
            // which is supporting the new magic version to one which doesn't, then we will need to convert.
            if (!records.hasMatchingMagic(minUsedMagic))
                records = batch.records().downConvert(minUsedMagic, 0);
            produceRecordsByPartition.put(tp, records);
            recordsByPartition.put(tp, batch);
        }

        String transactionalId = null;
        if (transactionManager != null && transactionManager.isTransactional()) {
            transactionalId = transactionManager.transactionalId();
        }
        ProduceRequest.Builder requestBuilder = new ProduceRequest.Builder(minUsedMagic, acks, timeout,
                produceRecordsByPartition, transactionalId);
        RequestCompletionHandler callback = new RequestCompletionHandler() {
            public void onComplete(ClientResponse response) {
                handleProduceResponse(response, recordsByPartition, time.milliseconds());
            }
        };

        String nodeId = Integer.toString(destination);
        ClientRequest clientRequest = client.newClientRequest(nodeId, requestBuilder, now, acks != 0, callback);
        client.send(clientRequest, now);
        log.trace("Sent produce request to {}: {}", nodeId, requestBuilder);
    }

这里需要注意的是,发送线程并不负责真正发送客户端请求, 它会从记录收集器中取出要发送的消息,创建好客户端请求,然后把请求交给客户端网络对象(NetworkClient)去发送。因为没有在发送线程中发送请求,所以创建客户端请求时需要保留目标节点,这样客户端网络对象获取出客户端请求时,才能知道要发送给哪个目标节点 。


相关推荐
<p> 需要学习Windows系统YOLOv4的同学请前往《Windows版YOLOv4目标检测实战:原理与源码解析》, </p> <p> 课程链接 https://edu.csdn.net/course/detail/29865 </p> <h3> <span style="color:#3598db;">【为什么要学习这门课】</span> </h3> <p> <span>Linux</span>创始人<span>Linus Torvalds</span>有一句名言:<span>Talk is cheap. Show me the code. </span><strong><span style="color:#ba372a;">冗谈不够,放码过来!</span></strong> </p> <p> <span> </span>代码阅读是从基础到提高的必由之路。尤其对深度学习,许多框架隐藏了神经网络底层的实现,只能在上层调包使用,对其内部原理很难认识清晰,不利于进一步优化和创新。 </p> <p> YOLOv4是最近推出的基于深度学习的端到端实时目标检测方法。 </p> <p> YOLOv4的实现darknet是使用C语言开发的轻型开源深度学习框架,依赖少,可移植性好,可以作为很好的代码阅读案例,让我们深入探究其实现原理。 </p> <h3> <span style="color:#3598db;">【课程内容与收获】</span> </h3> <p> 本课程将解析YOLOv4的实现原理和源码,具体内容包括: </p> <p> - YOLOv4目标检测原理<br /> - 神经网络及darknet的C语言实现,尤其是反向传播的梯度求解和误差计算<br /> - 代码阅读工具及方法<br /> - 深度学习计算的利器:BLAS和GEMM<br /> - GPU的CUDA编程方法及在darknet的应用<br /> - YOLOv4的程序流程 </p> <p> - YOLOv4各层及关键技术的源码解析 </p> <p> 本课程将提供注释后的darknet的源码程序文件。 </p> <h3> <strong><span style="color:#3598db;">【相关课程】</span></strong> </h3> <p> 除本课程《YOLOv4目标检测:原理与源码解析》外,本人推出了有关YOLOv4目标检测的系列课程,包括: </p> <p> 《YOLOv4目标检测实战:训练自己的数据集》 </p> <p> 《YOLOv4-tiny目标检测实战:训练自己的数据集》 </p> <p> 《YOLOv4目标检测实战:人脸口罩佩戴检测》<br /> 《YOLOv4目标检测实战:中国交通标志识别》 </p> <p> 建议先学习一门YOLOv4实战课程,对YOLOv4的使用方法了解以后再学习本课程。 </p> <h3> <span style="color:#3598db;">【YOLOv4网络模型架构图】</span> </h3> <p> 下图由白勇老师绘制 </p> <p> <img alt="" src="https://img-bss.csdnimg.cn/202006291526195469.jpg" /> </p> <p>   </p> <p> <img alt="" src="https://img-bss.csdnimg.cn/202007011518185782.jpg" /> </p>
<p> 欢迎参加英特尔® OpenVINO™工具套件初级课程 !本课程面向零基础学员,将从AI的基本概念开始,介绍人工智能与视觉应用的相关知识,并且帮助您快速理解英特尔® OpenVINO™工具套件的基本概念以及应用场景。整个课程包含了视频的处理,深度学习的相关知识,人工智能应用的推理加速,以及英特尔® OpenVINO™工具套件的Demo演示。通过本课程的学习,将帮助您快速上手计算机视觉的基本知识和英特尔® OpenVINO™ 工具套件的相关概念。 </p> <p> 为保证您顺利收听课程参与测试获取证书,还请您于<strong>电脑端</strong>进行课程收听学习! </p> <p> 为了便于您更好的学习本次课程,推荐您免费<strong>下载英特尔® OpenVINO™工具套件</strong>,下载地址:https://t.csdnimg.cn/yOf5 </p> <p> 收听课程并完成章节测试,可获得本课程<strong>专属定制证书</strong>,还可参与<strong>福利抽奖</strong>,活动详情:https://bss.csdn.net/m/topic/intel_openvino </p> <p> 8月1日-9月30日,学习完成【初级课程】的小伙伴,可以<span style="color:#FF0000;"><strong>免费学习【中级课程】</strong></span>,中级课程免费学习优惠券将在学完初级课程后的7个工作日内发送至您的账户,您可以在:<a href="https://i.csdn.net/#/wallet/coupon">https://i.csdn.net/#/wallet/coupon</a>查询优惠券情况,请大家报名初级课程后尽快学习哦~ </p> <p> <span style="font-size:12px;">请注意:点击报名即表示您确认您已年满18周岁,并且同意CSDN基于商务需求收集并使用您的个人信息,用于注册OpenVINO™工具套件及其课程。CSDN和英特尔会为您定制最新的科学技术和行业信息,将通过邮件或者短信的形式推送给您,您也可以随时取消订阅不再从CSDN或Intel接收此类信息。 查看更多详细信息请点击CSDN“<a href="https://passport.csdn.net/service">用户服务协议</a>”,英特尔“<a href="https://www.intel.cn/content/www/cn/zh/privacy/intel-privacy-notice.html?_ga=2.83783126.1562103805.1560759984-1414337906.1552367839&elq_cid=1761146&erpm_id=7141654/privacy/us/en/">隐私声明</a>”和“<a href="https://www.intel.cn/content/www/cn/zh/legal/terms-of-use.html?_ga=2.84823001.1188745750.1560759986-1414337906.1552367839&elq_cid=1761146&erpm_id=7141654/privacy/us/en/">使用条款</a>”。</span> </p> <p> <br /> </p>
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页