parallels desktop 网络初始化失败_Kafka源码深度剖析系列(十七)——Kafka网络设计...

7c87b923bf770a425ec59c9edb5f7539.png

-     本次目标     -

之前我们在分析KafkaProducer获取元数据的时候就涉及到了网络的部分,不过那个时候我们没有去分析关于网络的事,现在KafkaProducer开始要与Broker建立连接,发送数据了,所以我们不得不分析一下Kafka网络部分代码的设计。(要想看懂这篇内容需要有Java NIO方面的知识,如果这方面知识有欠缺的话,需要自己单独去补一下)

7c87b923bf770a425ec59c9edb5f7539.png

-     源码剖析     -

我们接着上篇的内容继续分析:
   public boolean ready(Node node, long now) {//node没有信息if (node.isEmpty())throw new IllegalArgumentException("Cannot connect to empty node " + node);//TODO broker是否达到发送数据的条件if (isReady(node, now))//如果broker达到发送数据的条件了,直接返回truereturn true;//如果代码走到这儿就表示broker没有建立好连接,还不可以发送数据//所以这儿判断的是否可以与broker建立连接//如果判断的结果是true,那么就进去初始化连接//当然正常情况下判断结果都是true,所以我们这一节就重点分析//如何初始化连接if (connectionStates.canConnect(node.idString(), now))// if we are interested in sending to a node and we don't have a connection to it, initiate one//初始化连接
            initiateConnect(node, now);return false;
    }
我们看一下初始化连接的代码:
private void initiateConnect(Node node, long now) {
        String nodeConnectionId = node.idString();try {log.debug("Initiating connection to node {} at {}:{}.", node.id(), node.host(), node.port());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.debug("Error connecting to node {} at {}:{}:", node.id(), node.host(), node.port(), e);
        }
    }
看到这儿我们发现,去做连接操作的是Selector对象。这个Selector对象是NetworkClient的一个成员变量。大家是否想起来在KafkaProducer初始化的时候也初始化了NetworkClient。我们看一下当时的代码:
private KafkaProducer(ProducerConfig config, Serializer keySerializer, Serializer valueSerializer) {
..............//NetworkClient是Kafka的核心的网络组建//底层核心的Selector负责最最核心的建立连接、发起请求、处理实际的网络IO
 NetworkClient client = new NetworkClient(
                    new Selector(config.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG), this.metrics, time, "producer", channelBuilder),this.metadata,
                    clientId,
                    config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION),
                    config.getLong(ProducerConfig.RECONNECT_BACKOFF_MS_CONFIG),
                    config.getInt(ProducerConfig.SEND_BUFFER_CONFIG),
                    config.getInt(ProducerConfig.RECEIVE_BUFFER_CONFIG),this.requestTimeoutMs, time);this.sender = new Sender(client,this.metadata,this.accumulator,
                    config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION) == 1,
                    config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG),
                    (short) parseAcks(config.getString(ProducerConfig.ACKS_CONFIG)),
                    config.getInt(ProducerConfig.RETRIES_CONFIG),this.metrics,
                    new SystemTime(),
                    clientId,this.requestTimeoutMs);
.........
}
我们看到了初始化NetworkClient的时候传进去了Selector对象,这个Selector是Kafka自己封装出来的一个类(如果熟悉NIO的应该知道Java NIO里面也有一个叫Selector的对象),它负责Kafka最最核心的建立网络连接、发起网络请求、处理实际的网络IO。 所以我们来分析一下这个类,看一下这个类的属性:
public class Selector implements Selectable {public static final long NO_IDLE_TIMEOUT_MS = -1;private static final Logger log = LoggerFactory.getLogger(Selector.class);//我们神奇的发现这个Selector 的nioSelector就是java nio里面的selector//也就是说Kafka的Selector就是对java nio的selector进行了封装//用户监听网络的I/O事件private final java.nio.channels.Selector nioSelector;//这里保存了每个broker id到Channel的映射关系,对于每个broker都有// 一个网络连接,每个连接在NIO的语义里,都有一个对应的SocketChannel,//KafkaChannel其实就是封装了SocketChannel,我们后面再去分析这个KafkaChannelprivate final Map channels;//已经成功发送出去的请求private final List completedSends;//已经接收回来的响应而且被处理完了private final List completedReceives;//每个Broker的收到的但是还没有被处理的响应private final Map> stagedReceives;//指的就是private final Set immediatelyConnectedKeys;//没有成功建立连接private final List disconnected;//成功建立连接private final List connected;//发送请求失败的brokerprivate final List failedSends;private final Time time;private final SelectorMetrics sensors;private final String metricGrpPrefix;private final Map metricTags;private final ChannelBuilder channelBuilder;private final int maxReceiveSize;private final boolean metricsPerConnection;private final IdleExpiryManager idleExpiryManager;/**
     * Create a new nioSelector
     *
     * @param maxReceiveSize Max size in bytes of a single network receive (use {@link NetworkReceive#UNLIMITED} for no limit)
     * @param connectionMaxIdleMs Max idle connection time (use {@link #NO_IDLE_TIMEOUT_MS} to disable idle timeout)
     * @param metrics Registry for Selector metrics
     * @param time Time implementation
     * @param metricGrpPrefix Prefix for the group of metrics registered by Selector
     * @param metricTags Additional tags to add to metrics registered by Selector
     * @param metricsPerConnection Whether or not to enable per-connection metrics
     * @param channelBuilder Channel builder for every new connection
     */public Selector(int maxReceiveSize,long connectionMaxIdleMs,
                    Metrics metrics,
                    Time time,
                    String metricGrpPrefix,
                    Map metricTags,boolean metricsPerConnection,
                    ChannelBuilder channelBuilder) {try {this.nioSelector = java.nio.channels.Selector.open();
        } catch (IOException e) {throw new KafkaException(e);
        }this.maxReceiveSize = maxReceiveSize;this.time = time;this.metricGrpPrefix = metricGrpPrefix;this.metricTags = metricTags;this.channels = new HashMap<>();this.completedSends = new ArrayList<>();this.completedReceives = new ArrayList<>();this.stagedReceives = new HashMap<>();this.immediatelyConnectedKeys = new HashSet<>();this.connected = new ArrayList<>();this.disconnected = new ArrayList<>();this.failedSends = new ArrayList<>();this.sensors = new SelectorMetrics(metrics);this.channelBuilder = channelBuilder;this.metricsPerConnection = metricsPerConnection;this.idleExpiryManager = connectionMaxIdleMs 0 ? null : new IdleExpiryManager(time, connectionMaxIdleMs);
    }
大家发现了没有,其实Kafka里的Selector 就是对java NIO里的Selector 进行了封装,说到java NIO就必然得谈一下SocketChannel了。在Kafka中Kafka基于原生的SocketChannel封装了一个KafkaChannel:
public class KafkaChannel {//broker id对应一个网络连接,一个网络连接对应一个KafkaChannel,// 底层对应的是SocketChannel,SocketChannel对应的是最最底层的网络通信层面的一个Socket,private final String id;//TODO 重要//我们之前猜想 KafkaChanel就是对原生的java nio封装了一下//但是我们进入到KafkaChannel的代码中发现,其实//没有发现SocketChannel的字眼,其实TransportLayer//又对KafkaChannel封装了一次private final TransportLayer transportLayer;private final Authenticator authenticator;private final int maxReceiveSize;//最近一次读出来的响应,先暂存在这里,这个值是会不断的变换的,因为会不断的读取新的响应数据private NetworkReceive receive;//发送出去的请求,这个值会不断发生变化,因为发送完一个请求需要发送下一个请求private Send send;public KafkaChannel(String id, TransportLayer transportLayer, Authenticator authenticator, int maxReceiveSize) throws IOException {this.id = id;this.transportLayer = transportLayer;this.authenticator = authenticator;this.maxReceiveSize = maxReceiveSize;
    }
我们看一下TransportLayer:
public interface TransportLayer extends ScatteringByteChannel, GatheringByteChannel {/**
     * Returns true if the channel has handshake and authentication done.
     */boolean ready();/**
     * Finishes the process of connecting a socket channel.
     */boolean finishConnect() throws IOException;/**
     * disconnect socketChannel
     */void disconnect();/**
     * Tells whether or not this channel's network socket is connected.
     */boolean isConnected();/**
     * returns underlying socketChannel
     * TODO 这个就是java里面的SocketChannel
     */SocketChannel socketChannel();/**
     * Performs SSL handshake hence is a no-op for the non-secure
     * implementation
     * @throws IOException
    */void handshake() throws IOException;/**
     * Returns true if there are any pending writes
     */boolean hasPendingWrites();/**
     * Returns `SSLSession.getPeerPrincipal()` if this is a SslTransportLayer and there is an authenticated peer,
     * `KafkaPrincipal.ANONYMOUS` is returned otherwise.
     */Principal peerPrincipal() throws IOException;void addInterestOps(int ops);void removeInterestOps(int ops);boolean isMute();/**
     * Transfers bytes from `fileChannel` to this `TransportLayer`.
     *
     * This method will delegate to {@link FileChannel#transferTo(long, long, java.nio.channels.WritableByteChannel)},
     * but it will unwrap the destination channel, if possible, in order to benefit from zero copy. This is required
     * because the fast path of `transferTo` is only executed if the destination buffer inherits from an internal JDK
     * class.
     *
     * @param fileChannel The source channel
     * @param position The position within the file at which the transfer is to begin; must be non-negative
     * @param count The maximum number of bytes to be transferred; must be non-negative
     * @return The number of bytes, possibly zero, that were actually transferred
     * @see FileChannel#transferTo(long, long, java.nio.channels.WritableByteChannel)
     */long transferFrom(FileChannel fileChannel, long position, long count) throws IOException;
}
根据上面分析我们得到Kafka的网络设计部分如下: 301c913fa01403c1e0bc769f67b5cedb.png 我们理解了上面的内容后,我们继续分析一开始看到的代码:
private void initiateConnect(Node node, long now) {
        String nodeConnectionId = node.idString();try {log.debug("Initiating connection to node {} at {}:{}.", node.id(), node.host(), node.port());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.debug("Error connecting to node {} at {}:{}:", node.id(), node.host(), node.port(), e);
        }
    }
根据我们刚刚的分析这个selector就是Kafka自己封装的selector,调用了它的connect方法,点过去:
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);//TODO 获取Socketchannel
        SocketChannel socketChannel = SocketChannel.open();//设置为非阻塞
        socketChannel.configureBlocking(false);//
        Socket socket = socketChannel.socket();
        socket.setKeepAlive(true);if (sendBufferSize != Selectable.USE_DEFAULT_BUFFER_SIZE)
            socket.setSendBufferSize(sendBufferSize);if (receiveBufferSize != Selectable.USE_DEFAULT_BUFFER_SIZE)
            socket.setReceiveBufferSize(receiveBufferSize);//TcpNoDelay,如果默认是设置为false的话,那么就开启Nagle算法,// 就是把网络通信中的一些小的数据包给收集起来,// 组装成一个大的数据包然后再一次性的发送出去,如果大量的小包在传递,会导致网络拥塞
        socket.setTcpNoDelay(true);
        boolean connected;try {/**
             *  如果此通道处于非阻塞模式,则调用
             *  方法启动非阻塞连接操作。如果连接
             *  立即建立,就像本地连接一样,然后
             *  此方法返回true,否则,此方法返回
             *  false连接操作稍后必须由
             *  调用@link finishConnect finishConnect方法。
             *///TODO 尝试去连接
            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;
        }//TODO 向Selector注册OP_CONNECT事件
        SelectionKey key = socketChannel.register(nioSelector, SelectionKey.OP_CONNECT);//TODO 封装KafkaChannel
        KafkaChannel channel = channelBuilder.buildChannel(id, key, maxReceiveSize);//关联
        key.attach(channel);this.channels.put(id, channel);//已经建立了连接if (connected) {// OP_CONNECT won't trigger for immediately connected channelslog.debug("Immediately connected to node {}", channel.id());
            immediatelyConnectedKeys.add(key);
            key.interestOps(0);
        }
    }
到目前KafkaProducer的运行流程如下: 535b32cdbf896dd3f2fbeb653388fd4c.png

7c87b923bf770a425ec59c9edb5f7539.png

-     总结     -

这一小节的内容有些复杂,大家跟着源码多理解几次,如果本小节的代码流程如果梳理不清楚的话,会影响到整个Kafka流程的学习,因为整个Kafka组件之间的网络通信就是用我们这讲的内容搭建的。这一小节我们分析了KafkaProducer的网络结构,尝试与Broker建立连接,但是还没正式建立上连接,下一次我们分析一下,KafkaProducer还没与Broker正式建立起连接,那么它会向Broker发送消息吗? 大家加油!! 9e2d660611dd4d48e69d71991c6af41a.png -   关注“大数据观止”   -

f1fae61668ac6f25cadfa1f88cb603c3.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值