大家都知道在 Java NIO 有个三剑客,即「 SocketChannel通道 」、「 Buffer读写 」、「 Selector多路复用器 」,上篇已经讲解了前2个角色,今天我们来聊聊最后一个重要的角色。
Kafka Selector 是对 Java NIO Selector 的二次封装,主要功能如下:
提供网络连接以及读写操作
对准备好的事件进行收集并进行网络操作
为了方便大家理解,所有的源码只保留骨干。
github 源码地址如下:
https://github.com/apache/kafka/blob/2.7/clients/src/main/java/org/apache/kafka/common/network/Selector.java
org.apache.kafka.common.network.Selector,该类是 Kafka 网络层最重要最核心的实现,也是非常经典的工业级通信框架实现,为了简化,这里称为 Kselector, 接下来我们先来看看该类的重要属性字段:
public class Selector implements Selectable, AutoCloseable {
// 在 Java NIO 中用来监听网络I/O事件
private final java.nio.channels.Selector nioSelector;
// channels 管理
private final Map channels;
// 发送完成的Send集合
private final List completedSends;
// 已经接收完毕的请求集合
private final LinkedHashMap completedReceives;
// 立即连接的集合
private final Set immediatelyConnectedKeys;
// 关闭连接的 channel 集合
private final Map closingChannels;
// 断开连接的节点集合
private final Map disconnected;
// 连接成功的节点集合
private final List connected;
// 发送失败的请求集合
private final List failedSends;
// 用来构建 KafkaChannel 的工具类
private final ChannelBuilder channelBuilder;
// 最大可以接收的数据量大小
private final int maxReceiveSize;
// 空闲超时到期连接管理器
private final IdleExpiryManager idleExpiryManager;
// 用来管理 ByteBuffer 的内存池
private final MemoryPool memoryPool;
// 初始化 Selector
public Selector(int maxReceiveSize,
long connectionMaxIdleMs,
int failedAuthenticationDelayMs,
Metrics metrics,
Time time,
String metricGrpPrefix,
Map metricTags,
boolean metricsPerConnection,
boolean recordTimePerConnection,
ChannelBuilder channelBuilder,
MemoryPool memoryPool,
LogContext logContext) {
try {
this.nioSelector = java.nio.channels.Selector.open();
} catch (IOException e) {
throw new KafkaException(e);
}
this.maxReceiveSize = maxReceiveSize;
this.time = time;
this.channels = new HashMap<>();
this.explicitlyMutedChannels = new HashSet<>();
this.outOfMemory = false;
this.completedSends = new ArrayList<>();
this.completedReceives = new LinkedHashMap<>();
this.immediatelyConnectedKeys = new HashSet<>();
this.closingChannels = new HashMap<>();
this.keysWithBufferedRead = new HashSet<>();
this.connected = new ArrayList<>();
this.disconnected = new HashMap<>();
this.failedSends = new ArrayList<>();
this.log = logContext.logger(Selector.class);
this.sensors = new SelectorMetrics(metrics, metricGrpPrefix, metricTags, metricsPerConnection);
this.channelBuilder = channelBuilder;
this.recordTimePerConnection = recordTimePerConnection;
this.idleExpiryManager = connectionMaxIdleMs < 0 ? null : new IdleExpiryManager(time, connectionMaxIdleMs);
this.memoryPool = memoryPool;
this.lowMemThreshold = (long) (0.1 * this.memoryPool.size());
this.failedAuthenticationDelayMs = failedAuthenticationDelayMs;
this.delayedClosingChannels = (failedAuthenticationDelayMs > NO_FAILED_AUTHENTICATION_DELAY) ? new LinkedHashMap() : null;
}
}
重要字段如下所示:
nioSelector:在 Java NIO 中用来监听网络I/O事件。
channels:用来进行管理客户端到各个Node节点的网络连接,Map 集合类型
completedSends:已经发送完成的请求对象 Send 集合,List 集合类型。
completedReceives:已经接收完毕的网络请求集合,LinkedHashMap 集合类型
immediatelyConnectedKeys:立即连接key集合。
closingChannels:关闭连接的 channel 集合。
disconnected:断开连接的集合。Map 集合类型
connected:成功连接的集合,List 集合类型,存储成功请求的 ChannelId。
failedSends:发送失败的请求集合,List 集合类型, 存储失败请求的 ChannelId。
channelBuilder:用来构建 KafkaChannel 的工具类。
maxReceiveSize:最大可以接收的数据量大小。
idleExpiryManager:空闲超时到期连接管理器。
memoryPool:用来管理 ByteBuffer 的内存池,分配以及回收。
介绍完字段后,我们来看看该类的方法。方法比较多,这里深度剖析下其中几个重要方法,通过学习这些方法的不仅可以复习下 Java NIO 底层组件,另外还可以学到 Kafka 封装这些底层组件的实现思想。
NetworkClient 的请求一般都是交给 Kselector 去处理并完成的。而 Kselector 使用 NIO 异步非阻塞模式负责具体的连接、读写事件等操作。
我们先看下连接过程,客户端在和节点连接的时候,会创建和服务端的 SocketChannel 连接通道。Kselector 维护了每个目标节点对应的 KafkaChannel。
如下图所示:
02.1 connect()
@Override
public void connect(String id, InetSocketAddress address, int sendBufferSize, int receiveBufferSize) throws IOException {
// 1.先确认是否已经被连接过
ensureNotRegistered(id);
// 2.打开一个 SocketChannel
SocketChannel socketChannel = SocketChannel.open();
SelectionKey key = null;
try {
// 3.设置 socketChannel 信息
configureSocketChannel(socketChannel, sendBufferSize, receiveBufferSize);
// 4.尝试发起连接
boolean connected = doConnect(socketChannel, address);
// 5. 将该 socketChannel 注册到 nioSelector 上,并关注 OP_CONNECT 事件
key = registerChannel(id, socketChannel, SelectionKey.OP_CONNECT);
// 6.如果立即连接成功了
if (connected) {
...
// 先将 key 放入 immediatelyConnectedKeys 集合
immediatelyConnectedKeys.add(key);
// 并取消对 OP_CONNECT 的监听
key.interestOps(0);
}
} catch (IOExcepti