前言
日常我们通过客户端向服务器请求数据或服务,服务器端则根据请求作出响应。客户端的不断增长需要服务器有更强的处理数据能力。事实上,客户端数量的增长速度已经远远高于服务器硬件的增加速度,这就很可能引起服务器的重负荷,继而产生性能瓶颈。从操作系统层面讲,服务器处理请求 响应 是一次处理 “读”和“写”过程,数据经过 磁盘-->内核缓冲-->应用程序-->socket缓冲-->网卡-->宽带-->手机客户端呈现到用户面前。
缓冲区的存在提高了数据的响应速度,但数据的重复流转占用了不必要的CPU性能和内存宽带。零拷贝技术可以解决数据的重复“搬运”问题,更有效的利用系统资源,从而提高服务器性能。
基本介绍
零拷贝是一种避免零拷贝CPU将数据从一块存储拷贝到另一块存储的技术。数据拷贝对CPU来讲是一种比较简单的任务,如果CPU的大部分时间在处理数据拷贝的任务上,复杂的逻辑处于等待状态,这将造成资源的浪费。
传统拷贝方式
下图是传统的拷贝方式:
上面是一个 数据从服务器传输到网上的流程,如web应用程序静态内容展示,视频网站流量,电影网站数据下载等应用场景。 可以看出 传统的拷贝流程 涉及到 两次DMA直接拷贝 和两次 CPU拷贝。 CPU在处理系统进程和应用进程时会进行上下文切换 下图是一个上下文切换的时序图:
总共经历了4次上下文切换:
1 用户请求读操作 CPU从服务器应用切换到内核
2 数据从内核缓存返回 CPU从内核缓存切换到服务器应用
3 数据从应用转出 CPU从应用切换到内核
4 数据传输到客户端后 服务器应用获得返回 CPU从内核切换到服务器应用
传统的拷贝流程 服务器应用进行了两次没有意义的拷贝和CPU上下文切换,即数据从磁盘拷贝到应用,再从应用拷出去,这样做对意义不大 但对于大数据量的操作 加重了系统负担。下面将介绍的零拷贝 将会解决此问题。
零拷贝方式
从传统的拷贝方式可以看出 服务器应用只是提供了数据的缓存功能,两次CPU拷贝是多余的,数据可以直接通过内核缓存拷贝到套接字缓存 省去了服务器应用的拷贝流程,如下图所示:
零拷贝方式是如何实现的呢? 这要借助于 Linux内核系统中的 方法:
sendfile(socket, file, len)
其中 socket 是要拷贝的套接字缓存地址,file是磁盘文件地址,len为要拷贝的文件大小
其中套接字缓存 仅仅保存了 读缓存内存地址 网卡缓存通过获取套接字保存的地址 直接从读缓存拷贝文件数据。
通过零拷贝 CPU的上下文切换也从传统拷贝的4次降到了2次,即 应用请求 应用-> 系统内核 ,请求返还 系统内核-应用
大大提升了性能。
两种拷贝模式 的性能比较 横轴为文件大小 纵轴为拷贝耗时
上面的零拷贝 是基于应用程序不会对 文件数据进行修改的情况下 ,如果要进行数据修改 数据还是要加载到 应用内存空间内, 如果应用程序能够直接操作 操作系统内核数据 则不需要拷贝到应用程序内,在Linux中使用mmp()方式完成应用程序数据直接写入内存。应用程序在调用mmp方法后 数据会先通过 DMA 拷贝到操作系统内核的缓冲区中去,应用程序和操作系统共享内核缓冲区,不需要拷贝 应用程序就可以操作内核缓冲区数据,write 数据完后 内核缓冲区的数据再拷贝到套接字缓冲区,完成拷贝操作
典型应用
操作系统底层的革新和优化会带来上层应用层巨大的提升 进而创造出五颜六色的世界。废话不说 直奔主题
Java NIO的零拷贝
接口java.nio.channels.FileChannel.transferTo(long position, long count,WritableByteChannel target)
使用方法:
//文件输入通道
FileChannel inputFileChannel = getInputFileChannel("Test.dmg");
//文件输出通道
FileChannel outputFileChannel = getOutFileChannel("postManNIO.dmg");
//long totalSize = inputFileChannel.transferTo(0,inputFileChannel.size(),outputFileChannel);
最终调的是FileChannelImpl JNI接口实现:
private native long transferTo0(FileDescriptor var1, long var2, long var4, FileDescriptor var6);
Netty的零拷贝
1. FileRegion
FileRegion 封装了 Java的java.nio.channels.FileChannel.transferTo接口
2. 应用层的零拷贝CompositeByteBuf
如果要对多段数据操作 正常情况下会 把多段数据组合到一起 系统开辟块内存空间 放进去再进行操作。CompositeByteBuf 是将多段数据 组合到一起 但是引用的还是原数据段 没有开辟内存空间。
Netty推荐使用 Upooled.wrappedBuffer(ByteBuffer... buffers) 方法 ,底层还是生成了CompositeByteBuf对象 如图
零拷贝 在互联网、大数据应用中有着广泛的应用,如 Web应用 静态文件,视频网站 流数据 及 软件下载等,开源框架也对零拷贝进行了支持 和扩展 ,原理就是 减少数据的转移环节,提高性能 ,如果有兴趣可以动手写个零拷贝程序实现
结语
5G时代已经来临,网路的传输速度可能达到 1G每秒的速度,到时候CPU的处理运算速度和处理能力将成为应用的瓶颈,怎样设计一个处理速度快,性能优良的应用架构,开发人员将会面临新的挑战。零拷贝通过减少数据拷贝次数,降低CPU上下文切换的次数 能够有效提升数据的处理速度 从而降低网络延迟 提升网络吞吐。让CPU 、每片内存空间物尽其用 也是每个开发人员面对的问题和挑战 。
参考资料
Zero Copy I: User-Mode Perspective ----https://www.linuxjournal.com/article/6345
zero copy技术图解 ------https://www.jianshu.com/p/8c6b056f73ce
Linux 中的零拷贝技术 -----https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/index.html
网友评论