零拷贝真的很重要!!!

零拷贝作为很多中间件和框架的核心知识,在很多场景有很多的应用,例如:Java NIO、Netty底层的通信协议有用到零拷贝,Kafka之所以这么快也用到了零拷贝,RocktMQ存储的核心也有用到零拷贝,所以面试的时候,是经常出现的,是重点中的重点!

一、IO的一些基本概念

1.1、缓冲区

缓冲区是所有I/O的基础,I/O讲的无非就是把数据移进或移出缓冲区;进程执行I/O操作,就是向操作系统发出请求,让它要么把缓冲区的数据排干(写),要么填充缓冲区(读);下面看一个Java进程发起read请求加载数据大致的流程图:

进程发起read请求之后,内核接收到read请求之后,会先检查内核空间中是否已经存在进程所需要的数据,如果已经存在,则直接把数据copy给进程的缓冲区如果没有内核随即向磁盘控制器发出命令,要求从磁盘读取数据,磁盘控制器把数据直接写入内核read缓冲区,这一步通过DMA完成;接下来就是内核将数据copy到进程的缓冲区;
如果进程发起write请求,同样需要把用户缓冲区里面的数据copy到内核的socket缓冲区里面,然后再通过DMA把数据copy到网卡中,发送出去;
你可能觉得这样挺浪费空间的,每次都需要把内核空间的数据拷贝到用户空间中,所以零拷贝的出现就是为了解决这种问题的;
关于零拷贝提供了两种方式分别是:mmap+write方式,sendfile方式,下边会说

2、虚拟内存

所有现代操作系统都使用虚拟内存,使用虚拟的地址取代物理地址,这样做的好处是:
1)一个以上的虚拟地址可以指向同一个物理内存地址,
2)虚拟内存空间可大于实际可用的物理地址;
利用第一条特性可以把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样DMA就可以填充对内核和用户空间进程同时可见的缓冲区了,大致如下图所示:

省去了内核与用户空间的往来拷贝,java也利用操作系统的此特性来提升性能,下面重点看看java对零拷贝都有哪些支持。

3、mmap+write方式

扫描二维码关注公众号,回复: 14318281 查看本文章

使用mmap+write方式代替原来的read+write方式,mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系;这样就可以省掉原来内核read缓冲区copy数据到用户缓冲区,但是还是需要内核read缓冲区将数据copy到内核socket缓冲区。

4、sendfile方式

sendfile系统调用在内核版本2.1中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。sendfile系统调用的引入,不仅减少了数据复制,还减少了上下文切换的次数。

数据传送只发生在内核空间,所以减少了一次上下文切换;但是还是存在一次copy,能不能把这一次copy也省略掉,Linux2.4内核中做了改进,将Kernel buffer中对应的数据描述信息(内存地址,偏移量)记录到相应的socket缓冲区当中,这样连内核空间中的一次cpu copy也省掉了;

1.2、IO的读取方式

IO的读写方式有中断DMA

1.2.1、中断方式

用户进程发起数据读取请求

系统调度为该进程分配cpu

cpu向io控制器(ide,scsi)发送io请求

用户进程等待io完成,让出cpu

系统调度cpu执行其他任务

数据写入至io控制器的缓冲寄存器

缓冲寄存器满了向cpu发出中断信号

cpu读取数据至内存

缺点:中断次数取决于缓冲寄存器的大小

1.2.2、DMA方式

用户进程发起数据读取请求

系统调度为该进程分配cpu

cpu向DMA发送io请求

用户进程等待io完成,让出cpu

系统调度cpu执行其他任务

数据写入至io控制器的缓冲寄存器

DMA不断获取缓冲寄存器中的数据(需要cpu时钟)

传输至内存(需要cpu时钟)

所需的全部数据获取完毕后向cpu发出中断信号

优点:减少cpu中断次数,不用cpu拷贝数据

1.3、数据拷贝

下面展示了传统方式读取数据后并通过网络发送所发生的数据拷贝

一个read系统调用后,DMA执行了一次数据拷贝,从磁盘到内核空间

read结束后,发生第二次数据拷贝,由cpu将数据从内核空间拷贝至用户空间

send系统调用,cpu发生第三次数据拷贝,由cpu将数据从用户空间拷贝至内核空间(socket缓冲区)

send系统调用结束后,DMA执行第四次数据拷贝,将数据从内核拷贝至协议引擎

另外,这四个过程中,每个过程都发生一次上下文切换

内存缓冲数据,主要是为了提高性能,内核可以预读部分数据,当所需数据小于内存缓冲区大小时,将极大的提高性能。

零拷贝是为了消除这个过程中冗余的拷贝

三、什么是零拷贝?

Zero-copy, 就是在操作数据时, 不需要将数据 buffer 从一个内存区域拷贝到另一个内存区域。因为少了一次内存的拷贝, 因此 CPU 的效率就得到的提升。

“零拷贝”,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。

操作系统:Zero-copy 通常指避免在用户态(User-space) 与内核态(Kernel-space) 之间来回拷贝数据。

3.1、零拷贝-sendfile 对应到java中

为FileChannel.transferTo(long position, long count, WritableByteChannel target)//将数据从文件通道传输到了给定的可写字节通道

避免了第二节中第2,3步的数据拷贝。

DMA从拷贝至内核缓冲区

cpu将数据从内核缓冲区拷贝至内核空间(socket缓冲区)

DMA将数据从内核拷贝至协议引擎

这三个过程中共发生2次上下文切换,分别为发起读取文件和发送数据

以上过程发生了三次数据拷贝,其中有一次为cpu完成

linux内核2.4以后,socket缓冲区做了调整,DMA带收集功能。

DMA从拷贝至内核缓冲区

将数据的位置和长度的信息的描述符增加至内核空间(socket缓冲区)

DMA将数据从内核拷贝至协议引擎

3.2、零拷贝-mmap 对应到java中

为MappedByteBuffer//文件内存映射

数据不会复制到用户空间,只在内核空间,与sendfile类似,但是应用程序可以直接操作该内存。

四、kafka零拷贝?

使用零拷贝之前:

可以看到,“零拷贝技术”只用将磁盘文件的数据复制到页面缓存中一次,然后将数据从页面缓存直接发送到网络中(发送给不同的订阅者时,都可以使用同一个页面缓存),避免了重复复制操作。

如果有10个消费者,传统方式下,数据复制次数为4*10=40次,而使用“零拷贝技术”只需要1+10=11次,一次为从磁盘复制到页面缓存,10次表示10个消费者各自读取一次页面缓存。

五、Netty零拷贝

 

传统方式:

 

 

猜你喜欢

转载自blog.csdn.net/luoyang_java/article/details/100142479