想了解kafka的零拷贝到底是什么,可以看一下Kafka为什么这么快?
如果看零拷贝和非拷贝之间的区别图可以看Kafka_Kafka中的Zero Copy
一、首先用图来展示kafka零拷贝的原理
可以看一下Kafka零拷贝,下面的图也是来自这个链接,
如果没有零拷贝,正常逻辑应该是下面这种,
如果有了零拷贝,就变成下面这张图,减少Application Buffer(即用户态)的结转
如果是生产者生产数据到磁盘,Read buffer
和Socket buffer
颠倒一下,箭头也转向一下,transferTo()
改成 write()
二、kakfa利用FileChannel实现了零拷贝
关于FileChannel的语法,可以看NIO详解(十):FileChannel零拷贝技术
1、消费者从broker拉取数据
消费者消费数据关于零拷贝源码实现可以看我整理的kakfa 3.5 kafka服务端处理消费者客户端拉取数据请求源码中第六章节
@Override
public long writeTo(TransferableChannel destChannel, long offset, int length) throws IOException {
long newSize = Math.min(channel.size(), end) - start;
int oldSize = sizeInBytes();
if (newSize < oldSize)
throw new KafkaException(String.format(
"Size of FileRecords %s has been truncated during write: old size %d, new size %d",
file.getAbsolutePath(), oldSize, newSize));
long position = start + offset;
long count = Math.min(length, oldSize - offset);
return destChannel.transferFrom(channel, position, count);
}
@Override
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
return fileChannel.transferTo(position, count, socketChannel);
}
transferTo()
方法直接将当前通道内容传输到另一个通道,没有涉及到Buffer的任何操作,NIO中的Buffer是JVM堆或者堆外内存,但不论如何他们都是操作系统内核空间的内存。也就是说这种方式不会有内核缓冲区到用户缓冲区的读写问题
transferTo()
的实现方式就是通过系统调用sendfile()
(当然这是Linux中的系统调用)
2、生产者生产数据到broker
这一部分可以看我整理的kafka 3.5 kafka服务端接收生产者发送的数据源码
/**
* 将一组记录追加到文件中。此方法不是线程安全的,必须使用锁进行保护。
*/
public int append(MemoryRecords records) throws IOException {
//代码检查要追加的记录的大小是否超过了当前文件位置之后的剩余空间大小,如果超过了,则抛出一个IllegalArgumentException异常。
if (records.sizeInBytes() > Integer.MAX_VALUE - size.get())
throw new IllegalArgumentException("Append of size " + records.sizeInBytes() +
" bytes is too large for segment with current file position at " + size.get());
//records.writeFullyTo(channel)方法将记录完全写入到指定的channel中,并返回实际写入的字节数。
int written = records.writeFullyTo(channel);
//ize.getAndAdd(written)方法将已写入的字节数添加到size变量中,并返回实际写入的字节数。
size.getAndAdd(written);
return written;
}
/**
* 将所有记录写入给定通道(包括部分记录)。
*/
public int writeFullyTo(GatheringByteChannel channel) throws IOException {
//保存当前缓冲区的位置
buffer.mark();
int written = 0;
//将缓冲区的内容写入到通道中,并将已写入的字节数累加到written变量中。
while (written < sizeInBytes())
written += channel.write(buffer);
//恢复缓冲区的位置到之前保存的位置。
buffer.reset();
return written;
}
/**
*将字节序列从给定缓冲区写入此通道。
*/
@Override
public int write(ByteBuffer src) throws IOException {
return socketChannel.write(src);
}
相当于直接把请求数据的ByteBuffer
(内核态,数据还没复制到用户态)通过FileChannel不用用户态和内核态相互之间的复制,直接转到socketChannel
Kafka 的数据传输通过 TransportLayer
来完成,其子类 PlaintextTransportLayer
通过Java NIO
的 FileChannel
的 transferTo 和 transferFrom
方法实现零拷贝