【网络核心层篇】NetworkClient—初始化连接

initiateConnect方法虽然初始化了一个连接,也执行了 doConnect(socketChannel, address);方法,

但是在 设置成了非阻塞I/O模式 见3, channel.connect(address);会立即返回。所以initiateConnect方法执行结束,并不代表真正的一个连接,建立,后面会在网络读写的poll方法中对连接进行检查,channel.finishConnect()

最终调用的socketChannel.finishConnect()方法检查。如果不可用会有下列异常:

在kafka 日志中出现 Give up sending metadata request since no node is available 和异常堆栈中的

org.apache.kafka.common.network.PlaintextTransportLayer.finishConnect(PlaintextTransportLayer.java:50)

就是这种原因的。

注意:PlaintextTransportLayer 的finishConnect 只是PLAINTEXT,协议,其他协议会是SslTransportLayer

1:NetworkClient initiateConnect
    /**
     * Initiate a connection to the given node
     * 对指定的node 初始化连接
     */
    private void initiateConnect(Node node, long now) {
        //获取nodeId
        String nodeConnectionId = node.idString();
        try {
            log.debug("Initiating connection to node {}", node);
            //设置连接状态为正在建立连接
            this.connectionStates.connecting(nodeConnectionId, now);
            //建立连接
            selector.connect(nodeConnectionId, new InetSocketAddress(node.host(), node.port()), this.socketSendBuffer,
                    this.socketReceiveBuffer);
        } catch (IOException e) {
            /* attempt failed, we'll try again after the backoff */
            //设置连接状态为断开连接
            connectionStates.disconnected(nodeConnectionId, now);
            /* maybe the problem is our metadata, update it */
            metadataUpdater.requestUpdate();
            log.warn("Error connecting to node {}", node, e);
        }
    }
2:selector connect 建立连接
/**
 * 建立连接,
 * <p>
 * Note that this call only initiates the connection, which will be completed on a future {@link #poll(long)}
 * call. Check {@link #connected()} to see which (if any) connections have completed after a given poll call.
 *
 * @param id                The id for the new connection
 * @param address           The address to connect to
 * @param sendBufferSize    The send buffer for the new connection
 * @param receiveBufferSize The receive buffer for the new connection
 * @throws IllegalStateException if there is already a connection for that id
 * @throws IOException           if DNS resolution fails on the hostname or if the broker is down
 */
@Override
 public void connect(String id, InetSocketAddress address, int sendBufferSize, int receiveBufferSize)
        throws IOException {
        ensureNotRegistered(id);
        /***
         * 获得SocketChannel
         * 
         * 这里使用的无参open,所以这里并没有真正的建立间接
         */
        SocketChannel socketChannel = SocketChannel.open();
        try {
            // 配置连接 见[3]
            configureSocketChannel(socketChannel, sendBufferSize, receiveBufferSize);
            // 建立连接
            boolean connected = doConnect(socketChannel, address);
            /**
             * 注册socketChannel 连接事件到selector  见 [4]
             * 
             * 这里就是经典的nio 网络模型实现    
             */
           SelectionKey key = registerChannel(id, socketChannel,SelectionKey.OP_CONNECT);

              if (connected) {
                // OP_CONNECT won't trigger for immediately connected channels
                log.debug("Immediately connected to node {}", id);
                // 将SelectionKey 维护起来
                immediatelyConnectedKeys.add(key);
                // 设置interest集合为 OP_READ
                key.interestOps(0);
            }
        } catch (IOException | RuntimeException e) {
            socketChannel.close();
            throw e;
        }
    }
3: 配置Socket
private void configureSocketChannel(SocketChannel socketChannel, int sendBufferSize, int receiveBufferSize)
        throws IOException {
    //非阻塞
    socketChannel.configureBlocking(false);
    Socket socket = socketChannel.socket();
    /**保持连接状态,可以设置TCP长连接保活
     *
     * 通过源码可以知道 最终设置了
     * SocketOptions.SO_KEEPALIVE
     *如果2小时内在此套接口的任一方向都没有数据交换,
     *TCP就自动给对方发一个 keepalive。
     *
     * 在一些nio框架中一般都是自定义心跳实现。可以更精细化的配置
     */
    socket.setKeepAlive(true);
    if (sendBufferSize != Selectable.USE_DEFAULT_BUFFER_SIZE)
       //设置最大的套接字发送缓冲字节,设置最大发送的buffer大小
        socket.setSendBufferSize(sendBufferSize);
    if (receiveBufferSize != Selectable.USE_DEFAULT_BUFFER_SIZE)
      // 设置最大接受 的buffer大小
        socket.setReceiveBufferSize(receiveBufferSize);
    /**不采用缓冲区,立即发送 ,实时性更高,
     * 这里我之前使用网络编程一般使用false,使用缓冲区来提升性能
     * 但是这里为什么使用true,好像跟延迟ack 有关系?
     * 这里还有待继续研究
     */
    socket.setTcpNoDelay(true);
}
4:注册SocketChannel和实例化channel
    private SelectionKey registerChannel(String id, SocketChannel socketChannel, int interestedOps) throws IOException {
        // 注册socketChannel事件 到nioSelector
        SelectionKey key = socketChannel.register(nioSelector, interestedOps);
        // 建立KafkaChannel
        KafkaChannel channel = buildAndAttachKafkaChannel(socketChannel, id, key);
        // 连接id --> channel维护关系
        this.channels.put(id, channel);
        return key;
    }
private KafkaChannel buildAndAttachKafkaChannel(SocketChannel socketChannel, String id, SelectionKey key)
    throws IOException {
    try {
        // KafkaChannel构建器构建channel
        KafkaChannel channel = channelBuilder.buildChannel(id, key, maxReceiveSize, memoryPool);
        // 为channel 绑定SelectionKey
        key.attach(channel);
        return channel;
    } catch (Exception e) {
        try {
            socketChannel.close();
        } finally {
            key.cancel();
        }
        throw new IOException("Channel could not be created for socket " + socketChannel, e);
    }
}

ChannelBuilder有三个实现类,分别代表不同的认证方式,

这里根据配置security.protocol使用的什么认证类型,从而在采用的是ChannelBuilder哪个实现类的方法。

public KafkaChannel buildChannel(String id, SelectionKey key, int maxReceiveSize, MemoryPool memoryPool)
    throws KafkaException {
    try {
        PlaintextTransportLayer transportLayer = new PlaintextTransportLayer(key);
        PlaintextAuthenticator authenticator = new PlaintextAuthenticator(configs, transportLayer, listenerName);
       
        return new KafkaChannel(id, transportLayer, authenticator, maxReceiveSize,
            memoryPool != null ? memoryPool : MemoryPool.NONE);
    } catch (Exception e) {
        log.warn("Failed to create channel due to ", e);
        throw new KafkaException(e);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值