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);
}
}

1086

被折叠的 条评论
为什么被折叠?



