这是一道高频面试题,在javaNIO、kafka、Netty、Linux等很多技术中都会用到。作为一个非常重要的知识点,又是高频面试题,有必要从头好好理解。好了,开始今天的文章吧。1、什么是零拷贝?1.先从一个案例说起。为了解释这个概念,让我们从一个需求开始。有一天,某领导给你发了一个任务,让你完成一个从文件中读取数据的任务。并传输到网络上的一个小程序。代码很简单:首先我们在操作系统中找到这个文件,然后先将数据读入缓冲区,最后将缓冲区中的数据发送到网络。代码很简单,现在我们考虑一下数据从计算机传输到网络的整个过程:现在可以看到1->2->3->4的整个过程一共经历了四次拷贝,但是real是第二次和第三次比较耗资源浪费时间,因为这两次都需要经过我们的CPU拷贝,还需要在内核态和用户态之间来回切换。想想我们的CPU资源是多么宝贵,要处理大量的任务。还需要复制大量数据。如果能去掉两份CPU该多好!!!既可以节省CPU资源,又可以避免内核态和用户态的切换。先说说用户态和内核态的区别:在用户态执行时,一个进程所能访问的内存空间和对象是有限的,它占用的处理器是可以被抢占的。当在内核态执行时,那么所有的内存空间和对象都可以被访问,被占用的处理器是不允许被抢占的。2、优化方案需要去除第2次和第3次之间的副本。Linux开发者很早就注意到了这个问题,所以在Linux2.1内核中,增加了“数据复制到socket缓冲区”的动作,所以我们的javaNIO可以直接调用transferTo()方法来实现这个现象。现在来看,感觉性能资源有了很大的提升,但还不够完善。因为这三个副本也是用到了CPU的copy技术,这是第二次。不过别担心。Linux开发人员比我们更有远见。3、零拷贝优化方案在Linux2.4内核中进行了优化。相反,仅包含有关数据位置和长度信息的描述符被附加到套接字缓冲区。DMA引擎将数据直接从内核缓冲区传输到协议引擎,从而消除了最后的CPU副本。经过以上过程,数据只经过两份就从磁盘发送了出来。这才是真正的Zero-Copy注意:这里的zero-copy其实是按照内核态来划分的。这里没有CPU副本。数据在用户态经历了零拷贝,所以称为零拷贝。但不代表不能抄袭。好的。了解了零拷贝技术是什么之后,我们就来说说那些使用了零拷贝技术的数据结构。2、零拷贝技术会用在什么地方?一、java的NIO先说java,因为需要为后面的netty做铺垫。JavaNIO中的通道相当于操作系统内核空间(kernelspace)的缓冲区,而缓冲区(Buffer)对应操作系统用户空间(userspace)中的用户缓冲区(userbuffer)。堆外内存(DirectBuffer)在使用后需要应用程序手动回收,而堆内存(HeapBuffer)中的数据在GC时可能会自动回收。因此,在使用HeapBuffer读写数据时,为了避免GC导致缓冲区数据丢失,NIO会先将HeapBuffer内部的数据拷贝到DirectBuffer中一块临时的本地内存(nativememory)中。本次拷贝涉及到sun.misc.Unsafe.copyMemory()调用背后的实现原理与memcpy()类似。最后将临时生成的DirectBuffer内部数据的内存地址传递给I/O调用函数,避免了访问Java对象来处理I/O读写。(1)MappedByteBufferMappedByteBuffer是NIO提供的一种基于内存映射(mmap)零拷贝方式的实现,意思是从一个文件的position位置开始,将一个size大小的区域映射到一个内存映像文件中。这会添加地址映射而不复制它们。(2)DirectByteBufferDirectByteBuffer的对象引用位于Java内存模型的堆中。JVM可以对DirectByteBuffer对象进行内存分配和回收管理。是MappedByteBuffer的具体实现类。因此,它也有零拷贝技术。(3)FileChannelFileChannel定义了两个抽象方法transferFrom()和transferTo(),通过通道间建立连接来实现数据传输。直接看linux2.4的版本,socketbuffer做了调整,DMA有了collection功能。(1)DMA将数据从内核复制到内核缓冲区(2)将数据的位置和长度的描述符添加到内核空间(socketbuffer)(3)DMA将数据从内核复制到协议引擎。这个复制过程是零复制过程。2、NettyNetty中的零拷贝和上面说的操作系统层面的零拷贝是不一样的。我们所说的Netty零拷贝是完全基于(Java级别)用户态的。(1)Netty通过DefaultFileRegion类封装了FileChannel的tranferTo()方法,相当于通过java间接进行了零拷贝。(2)我们的数据传输一般都是通过TCP/IP协议来实现的。在实际应用中,很可能一个完整的报文会被分成多个数据包进行网络传输,单个数据包对你来说并不重要。没有意义,只有当这些数据包组成一个完整的消息时你才能做出正确的处理,而Netty可以零拷贝的方式将这些数据包组合成一个完整的消息供你使用。此时零拷贝的作用范围只是在用户空间。Netty是如何实现的呢?为此,我们需要找到Netty进行数据传输的接口。该接口必须包含可以实现零拷贝的功能。这个接口就是ChannelBuffer。既然有接口,就必然有实现类。最重要的实现类之一是CompositeChannelBuffer。该类的主要作用是将多个ChannelBuffer组合成一个虚拟的ChannelBuffer进行操作。为什么是虚拟的?因为CompositeChannelBuffer并没有真正组合多个ChannelBuffer,只是保存了它们的引用,从而避免了数据拷贝,实现了ZeroCopy。(3)ByteBuf可以通过wrap操作将字节数组、ByteBuf、ByteBuffer包装成一个ByteBuf对象,从而避免复制操作(4)ByteBuf支持slice操作,因此ByteBuf可以分解为多个ByteBuf共享同一个存储区,避免内存拷贝3、kafkaKafka的索引文件使用mmap+write方式,数据文件使用sendfile方式。适用于系统日志消息等高吞吐量大块文件的数据持久化和传输。如果有10个消费者,传统的方式,数据拷贝次数是4*10=40次,但是使用“零拷贝技术”只需要1+10=11次,一次从磁盘拷贝到页面cache,10次表示10个消费者每人读取一次pagecache。好的,你先过来。本文转载自微信公众号“愚公要移山”,可关注下方二维码。转载本文请联系愚公移山公众号。
