<KAFKA技术内幕> 二

1.真正发送消息 Sender 1.获取元数据 2.判断那些分区可以发送消息3.标识还没有拉取到元数据得topic 4.检查与主机发送数据网络是否连接好,5.从消息器获取队列准备发送6.对超时批次如何处理7.创建发送请求8.政治发送请求调用netty。

void run(long now) {

        /** 获取元数据
         *    因为我们是根据场景驱动的方式,目前是我们第一次代码进来,
         *    目前还没有获取到元数据
         *    所以这个cluster里面是没有元数据
         *    如果这儿没有元数据的话,这个方法里面接下来的代码就不用看了
         *    是因为接下来的这些代码依赖这个元数据。
         *    TODO 我们直接看这个方法的最后一行代码
         *    就是这行代码去拉取的元数据。
         */
        /**
         * 我们用场景驱动的方式,现在我们的代码是第二次进来
         * 第二次进来的时候,已经有元数据了,所以cluster这儿是有元数据。
         * 步骤一:
         *      获取元数据
         */
        Cluster cluster = metadata.fetch();
        // get the list of partitions with data ready to send
        /**
         * 步骤二:
         *      首先是判断哪些partition有消息可以发送,获取到这个partition的leader partition对应的broker主机。
         *      哪些broker上面需要我们去发送消息?
         */
        RecordAccumulator.ReadyCheckResult result = this.accumulator.ready(cluster, now);

        /**
         * 步骤三:
         *      标识还没有拉取到元数据的topic
         */
        // 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)) {
                //如果返回的是false  !false 代码就进来
                //移除result 里面要发送消息的主机。
                //所以我们会看到这儿所有的主机都会被移除
                iter.remove();
                notReadyTimeout = Math.min(notReadyTimeout, this.client.connectionDelay(node, now));
            }
        }

        // create produce requests
        /**
         * 步骤五:
         *
         * 我们有可能要发送的partition有很多个,
         * 很有可能有一些partition的leader partition是在同一台服务器上面。
         * p0:leader:0
         * p1:leader: 0
         * p2:leader: 1
         * p3:leader: 2
         *      假设我们集群只有3台服务器
         * 当我们的分区的个数大于集群的节点的个数的时候,一定会有多个leader partition在同一台服务器上面。
         *
         * 按照broker进行分组,同一个broker的partition为同一组
         * 0:{p0,p1}
         * 1:{p2}
         * 2:{p3}
         */
        // 如果网络没有建立好,这的代码是不执行的
        Map<Integer, List<RecordBatch>> batches = this.accumulator.drain(cluster,
                                                                         result.readyNodes,
                                                                         this.maxRequestSize,
                                                                         now);
        if (guaranteeMessageOrder) {
            // Mute all the partitions drained
            for (List<RecordBatch> batchList : batches.values()) {
                //如果batches 空的话,这而的代码也就不执行了。
                for (RecordBatch batch : batchList)
                    this.accumulator.mutePartition(batch.topicPartition);
            }
        }

        /**
         * 步骤六:
         *  对超时的批次是如何处理的?
         */
        List<RecordBatch> expiredBatches = this.accumulator.abortExpiredBatches(this.requestTimeout, now);
        // update sensors
        for (RecordBatch expiredBatch : expiredBatches)
            this.sensors.recordErrors(expiredBatch.topicPartition.topic(), expiredBatch.recordCount);

        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);
            pollTimeout = 0;
        }

        /**
         * 步骤七:
         *      创建发送消息的请求
         * 创建请求
         * 我们往partition上面去发送消息的时候,有一些partition他们在同一台服务器上面
         * ,如果我们一分区一个分区的发送我们网络请求,那网络请求就会有一些频繁
         * 我们要知道,我们集群里面网络资源是非常珍贵的。
         * 会把发往同个broker上面partition的数据 组合成为一个请求。
         * 然后统一一次发送过去,这样子就减少了网络请求。
         */
        //如果网络连接没有建立好 batches其实是为空。
        //也就说其实这段代码也是不会执行。
        sendProduceRequests(batches, now);

        // 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;
        /**
         * 步骤八:
         * 真正执行网络操作的都是这个NetWorkClient这个组件
         * 包括:发送请求,接受响应(处理响应)
         */
        // 我们猜这儿可能就是去建立连接。
        this.client.poll(pollTimeout, now);
    }

二、具体图示  一开始是生产者创建实列,与服务器连接获取原消息并写入到记录收集器,发送线程调用read方法找到准备好的服务节点,并建立连接消息满准备发送,从记录收集器得到批记录后,建立客户端请求NetworkClient

三NetworkClien
ready()方法 。从记录收集器获取准备完毕的节点,并连接所有准备好 节点
send()方法 。为每个节点 个客户端请求 将请求暂存 节点对应 通道中;
poll()方法 。轮询动作会真正 络请求, 比如发送请求给节点,并读取响应        

 
发送线程 run()方法的最后一步是调用NetwokClient poll()方法 轮询的最关键步骤是调用
selector.poll()方法,而在轮询之后,定义了多个处理方法 轮询不仅仅会发送客户端请求,也会接
收客户端响应 客户端发送请求后会调 handleCollpletedSends()处理已经完成的发送,客户端接收
到响应后会调用handleCollpletedReceives()处理已经完成的接收
如果客户端发送完请求不需要响应,在处理已经完成的发送时,就会将对应的请求从
iFlightRequests 队列中移踪 而因为没有响应结果,也就不会有机会调用handleCollpletedReceives()

      * 步骤八:
         * 真正执行网络操作的都是这个NetWorkClient这个组件
         * 包括:发送请求,接受响应(处理响应)
         */
        // 我们猜这儿可能就是去建立连接。
        this.client.poll(pollTimeout, now);

四 梳理一下send 请求响应同时处理

 客户端 服务端交互

五 select处理网络请求

Sender调用Networkclient连接发送轮询,Select的连接发送轮询

 public void connect(String id, InetSocketAddress address, int sendBufferSize, int receiveBufferSize) throws IOException {
        if (this.channels.containsKey(id))
            throw new IllegalStateException("There is already a connection for id " + id);

        //要是了解java NIO编程的同学,这些代码就是一些基本的
        //代码,跟我们NIO编程的代码是一模一样。

        //获取到SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        //设置为非阻塞的模式
        socketChannel.configureBlocking(false);
        Socket socket = socketChannel.socket();
        socket.setKeepAlive(true);
        //设置一些参数
        //这些网络的参数,我们之前在分析Producer的时候给大家看过
        //都有一些默认值。
        if (sendBufferSize != Selectable.USE_DEFAULT_BUFFER_SIZE)
            socket.setSendBufferSize(sendBufferSize);
        if (receiveBufferSize != Selectable.USE_DEFAULT_BUFFER_SIZE)
            socket.setReceiveBufferSize(receiveBufferSize);

        //这个的默认值是false,代表要开启Nagle的算法
        //它会把网络中的一些小的数据包收集起来,组合成一个大的数据包
        //然后再发送出去。因为它认为如果网络中有大量的小的数据包在传输
        //其实是会影响网络拥塞。

        //kafka一定不能把这儿设置为false,因为我们有些时候可能有些数据包就是比较
        //小,他这儿就不帮我们发送了,显然是不合理的。
        socket.setTcpNoDelay(true);
        boolean connected;
        try {
            //尝试去服务器去连接。
            //因为这儿非阻塞的
            //有可能就立马连接成功,如果成功了就返回true
            //也有可能需要很久才能连接成功,返回false。
            connected = socketChannel.connect(address);
        } catch (UnresolvedAddressException e) {
            socketChannel.close();
            throw new IOException("Can't resolve address: " + address, e);
        } catch (IOException e) {
            socketChannel.close();
            throw e;
        }

        //SocketChannel往Selector上注册了一个OP_CONNECT
        // 注册连接事件,创建底层的 transpotlaye 通道与连接标识
        SelectionKey key = socketChannel.register(nioSelector, SelectionKey.OP_CONNECT);
        //根据根据SocketChannel 封装出来一个KafkaChannel
        KafkaChannel channel = channelBuilder.buildChannel(id, key, maxReceiveSize);

        //把key和KafkaChannel关联起来
        //后面使用起来会比较方便
        //我们可以根据key就找到KafkaChannel
        //也可以根据KafkaChannel找到key
        key.attach(channel);
        //缓存起来了
        this.channels.put(id, channel);
        //所以正常情况下,这儿网络不能完成连接。
        //如果这儿不能完成连接。大家猜一下
        //kafka会在哪儿完成网络最后的连接呢?

        if (connected) {
            // OP_CONNECT won't trigger for immediately connected channels
            log.debug("Immediately connected to node {}", channel.id());
            immediatelyConnectedKeys.add(key);
            // 取消前面注册 OP_CONNECT 事件。
            key.interestOps(0);
        }
    }

构建kafka通道的传输层有多种实现:比如纯文本、sasl模式、ssl加密模式。

通道建立好就是字节传输  ByteBuffSend和NetworkReceive

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值