传统读操作JAVA用传统方式进行读操作时,整体流程如上图,具体如下:
我们看一下一个读操作,发了2次上下文切换,和2次数据copy,一次是DMA Copy,一次是CPU Copy。
传统写操作上图是JAVA传统的写操作,具体流程:
传统IO我们可以看出传统的IO读写操作,总共进行了4次上下文切换,4次Copy动作。我们可以看到数据在内核空间和应用空间之间来回复制,其实他们什么都没有做,就是复制而已,这个机制太浪费时间了,而且是浪费的CPU的时间。 那我们能不能让数据不要来回复制呢?零拷贝这个技术就是来解决这个问题。关于零拷贝提供了两种解决方式:mmap+write方式、sendfile方式 虚拟内存所有现代操作系统都使用虚拟内存,使用虚拟地址取代物理地址,这样做的好处就是:
我们利用第一条特性可以优化一下上面的设计思路,就是把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样就不需要来回复制了,看图: mmap+write方式使用mmap+write方式替换原来的传统IO方式,就是利用了虚拟内存的特性,看图 整体流程的核心区别就是,把数据读取到内核缓冲区后,应用程序进行写入操作时,直接是把内核的Read Buffer的数据 复制到 Socket Buffer 以便进行写入,这次内核之间的复制也是需要CPU参与的。
这个流程就少了一个CPU Copy,提升了IO的速度。不过发现上下文的切换还是4次,没有减少,因为还是要应用程序发起write操作。那能不能减少上下文切换呢? sendfile方式这种方式可以替换上面的mmap+write方式,如: mmap();write(); 替换为 sendfile(); 这样就减少了一次上下文切换,因为少了一个应用程序发起write操作,直接发起sendfile操作。 到这里就只有3次Copy,其中只有1次CPU Copy;3次上下文切换。那能不能把CPU Copy减少到没有呢? gatherLinux2.4内核进行了优化,提供了gather操作,这个操作可以把最后一次CPU Copy去除,什么原理呢?就是在内核空间Read Buffer和Socket Buffer不做数据复制,而是将Read Buffer的内存地址、偏移量记录到相应的Socket Buffer中,这样就不需要复制(其实本质就是和虚拟内存的解决方法思路一样,就是内存地址的记录),如图: JAVA零拷贝java nio实现零拷贝,JAVA提供了一下方法类: 1、MappedByteBuffer2、DirectByteBuffer3、FileChannel.transferTo 具体如何使用,小伙伴们上网自行查阅。 总结零拷贝技术在很多中间件中,都有利用;如:Kafka,Spark、RocketMQ等,这个在网络传输数据时,能够提升速度,提升系统性能、吞吐量。小伙伴们不一定会编写,可以先了解基本原理就行。很多好的中间件产品都需要了解一些计算机原理方面的知识,才会更深入的理解。 |
|