KafkaChannel是对SocketChannel的封装,是Kafka中负责网络读写的最底层类。封装了SocketChannel,还封装了Kafka自己的认证器Authenticator。屏蔽Kafka的上层逻辑,来看KafkaChannel是如何设计的。
KafkaChannel网络读写模型:
Kafka-client是nio selector模型的网络通信,自然KafkaChannel读写也是面向缓冲区的。KafkaChannel的写之前会向内存池开辟堆外内存,作为写缓冲区,缓冲区承载数据,缓冲区的结构是头部使用4个byte的int类型表示该缓冲区长度,头部后面为写入的实际数据。写的时候,将写缓冲区写入与指定node建立的SocketChannel。
读的之前,同样先回申请开辟一个读缓冲区,然后SocketChanne将读取数据到读缓冲区。
问题:为什么缓冲区需要一个size头部?
为了解决TCP粘包拆包问题。粘包/拆包问题是TCP编程必然解决的问题,TCP这种底层流协议。他根本无法感知上层业务数据边界,所以需要进行粘包拆包设计。主要还是采取使用一个size来表示消息长度,这样在读取的时候,一旦读取到消息的长度,就可以知道应该读取多长的流,就能解决粘包问题。当读取长度不够size,就持续接受。就解决了拆包问题。在一些TCP框架里面还会扩充一些其他分割器方案,比如netty里面提供了定长分割器和分隔符分割器。在Kafka里面,在是4个字节的来表示消息长度,使用的int变量。TCP不是本文分析的重点,就点到为止。下面细细分析KafkaChannel
通过主要method来看看KafkaChannel有哪些功能承担了哪些事情?
Method | 作用 |
---|---|
void prepare() | 握手认证 |
KafkaPrincipal principal() | 获取认证信息 |
boolean finishConnect() | 是否建立连接完成 |
boolean isConnected() | 是否建立连接 |
SelectionKey selectionKey() | 获取该channel上的SelectionKey |
void setSend(Send send) | 初始化需要发送的数据 |
NetworkReceive read() | 网络读(读取Kafka服务器数据) |
Send write() | 网络写(向Kafka服务器发送数据) |
void disconnect() | 断开连接 |
void state(ChannelState state) | 设置该连接的连接状态 |
ChannelState state() | 获取该连接的连接状态 |
3.1setSend(Send send)源码分析
public void setSend(Send send) {
if (this.send != null)
throw new IllegalStateException("Attempt to begin a send operation with prior send operation still in progress, connection id is " + id);
this.send = send;
//添加到interestOps [见下]
this.transportLayer.addInterestOps(SelectionKey.OP_WRITE);
}
public void addInterestOps(int ops) {
//SelectionKey
key.interestOps(key.interestOps() | ops);
}
setSend(Send send)就是将需要发送的数据设置到KafkaChannel的send成员中。(是在Kafka的selector的send方法中。关于kafka的selector,还会专门分析)然后给让selector增加写监控事件。Send 就是需要要发送的数据。
主要是来分析Send 是如何构建的。Send 是最上层接口。producer发送消息时候采用的是子类NetworkSend
3.2NetworkSend的构建
public class NetworkSend extends ByteBufferSend {
public NetworkSend(String destination, ByteBuffer buffer) {
//见下段分析
super(destination, sizeDelimit(buffer));
}
/**
* 组装ByteBuffer[size|data]的结构
* @param buffer
* @return
*/
private static ByteBuffer[] sizeDelimit(ByteBuffer buffer) {
return new ByteBuffer[]{sizeBuffer(buffer.remaining()), buffer};
}
/**
* 实例化4个字节的ByteBuffer,使用int初始化
* @param size
* @return
*/
private static ByteBuffer sizeBuffer(int size) {
ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
sizeBuffer.putInt(size);
//倒带这个sizeBuffer,重置索引
sizeBuffer.rewind();
return sizeBuffer;
}
}
public ByteBufferSend(String destination, ByteBuffer... buffers) {
this.destination = destination;
this.buffers = buffers;
//计算总长度
for (ByteBuffer buffer : buffers)
remaining += buffer.remaining();
this.size = remaining;
}
NetworkSend构建就能看出一个发往的的目的地destination和将buffer在sizeDelimit变成一个 ByteBuffer[],然后在父类ByteBufferSend中进行初始化。
ByteBuffer[]为两个buffer,可以理解为一个消息头buffer,一个消息体buffer。消息头buffer的长度为4byte,存放的时候是消息体buffer的长度。而消息体buffer是上层传入的业务数据,所以send就是持有一个待发送的 ByteBuffer[]。
3.3向服务器发送数据write()源码分析
/**
* 网络写
* 发送完成则返回send,
* 没有发送完成返回null
* @return
* @throws IOException
*/
public Send write() throws IOException {
Send result = null;
//发送
if (send != null && send(send)) {
result = send;
send = null;
}
return result;
}
private boolean send(Send send) throws IOException {
//这里使用的NetWorkSend,网络写,将数据写入SOCKET 【见下段】
send.writeTo(transportLayer);
//写结束,buffer内容写完表示结束,
if (send.completed())
//移除selectorKey的感兴趣集合写事件,
transportLayer.removeInterestOps(SelectionKey.OP_WRITE);
return send.completed();
}
write()的返回不为null则发送完成。send.writeTo(transportLayer); 进行数据写入。其中发送完成的依据是发送没有被挂起,并且剩余可发送的为0.
@Override
public long writeTo(GatheringByteChannel channel) throws IOException {
//将内容写进buffer,这里PlaintextTransportLayer ---> socketChannel.write(srcs);
long written = channel.write(buffers);
if (written < 0)
throw new EOFException("Wrote negative bytes to channel. This shouldn't happen.");
//更新剩余可发送的
remaining -= written;
pending = TransportLayers.hasPendingWrites(channel);
return written;
}
3.4网络读 NetworkReceive read()源码分析
/**
*网络读
* @return
* @throws IOException
*/
public NetworkReceive read() throws IOException {
NetworkReceive result = null;
if (receive == null) {
//构造一个NetworkReceive数据读取容器
receive = new NetworkReceive(maxReceiveSize, id, memoryPool);
}
//读取数据
receive(receive);
//数据读结束
if (receive.complete()) {
//重置消息体buffer指针
receive.payload().rewind();
result = receive;
receive = null;
} else if (receive.requiredMemoryAmountKnown() && !receive.memoryAllocated() && isInMutableState()) {
//pool must be out of memory, mute ourselves.
mute();
}
return result;
}
public NetworkReceive(int maxSize, String source, MemoryPool memoryPool) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = maxSize;
this.memoryPool = memoryPool;
}
这里的maxReceiveSize是限定读取的大小最大读取size,读取消息头中的消息长度大于该值,将会抛出InvalidReceiveException异常,但实际上通过producer过来的数据。KafkaChannel是在selector中初始化的。使用的是selector的maxReceiveSize,这个值在producer核心构造方法中使用的是NetworkReceive.UNLIMITED。
也就是没有限定长度大小,也就是虽然在底层KafkaChannel中提供了maxReceiveSize的校验,但是实际上是没有校验的。
receive(receive)分析
private long receive(NetworkReceive receive) throws IOException {
return receive.readFrom(transportLayer);
}
/***
* 读取Tcp数据
* 通过源码可以查看出拆包策略
* @param channel The channel to read from
* @return
* @throws IOException
*/
@Override
public long readFrom(ScatteringByteChannel channel) throws IOException {
int read = 0;
//是否读取到了数据长度
if (size.hasRemaining()) {
int bytesRead = channel.read(size);
if (bytesRead < 0)
throw new EOFException();
read += bytesRead;
//数据长度位读取到了,
if (!size.hasRemaining()) {
size.rewind();
//获取数据的长度
int receiveSize = size.getInt();
//长度进行校验
if (receiveSize < 0)
throw new InvalidReceiveException("Invalid receive (size = " + receiveSize + ")");
if (maxSize != UNLIMITED && receiveSize > maxSize)
throw new InvalidReceiveException("Invalid receive (size = " + receiveSize + " larger than " + maxSize + ")");
requestedBufferSize = receiveSize;
if (receiveSize == 0) {
buffer = EMPTY_BUFFER;
}
}
}
//网络读取数据
if (buffer == null && requestedBufferSize != -1) {
//按照读取的数据长度开辟堆外内存 buffer
buffer = memoryPool.tryAllocate(requestedBufferSize);
if (buffer == null)
log.trace("Broker low on memory - could not allocate buffer of size {} for source {}", requestedBufferSize, source);
}
//buffer已经分配了,表明size读取完
if (buffer != null) {
//从socket读取指定长度的内容
int bytesRead = channel.read(buffer);
if (bytesRead < 0)
throw new EOFException();
read += bytesRead;
}
return read;
}
;
}
//buffer已经分配了,表明size读取完
if (buffer != null) {
//从socket读取指定长度的内容
int bytesRead = channel.read(buffer);
if (bytesRead < 0)
throw new EOFException();
read += bytesRead;
}
return read;
}
关于KafkaChannel的更详细源码细节,可点击下载源码注释版进行查看githup记得给star