【网络核心层篇】-KafkaChannel-网络读写底层实现

1.KafkaChannel简述

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

2: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.KafkaChannel网络读写的源码分析

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值