零拷贝基本介绍

  1. 零拷贝是网络编程的关键,很多性能优化都离不开
  2. 在Java程序中,常用的零拷贝有mmap(内存映射)和sendFile。那么,他们在OS里,到底是怎么样一个涉及?我们分析mmap和sendFile这两个两拷贝
  3. 另外我们看下NIO中如何使用零拷贝

传统IO数据读写

  1. Java传统IO和网络编程的一段代码
 File file = new File("index.html");RandomAccessFile raf = new RandomAccessFile(file, "rw");byte[] arr = new byte[(int) file.length()];raf.read(arr);Socket socket = new ServerSocket(8080).accept();socket.getOutputStream().write(arr);

我们会调用 read 方法读取 index.html 的内容—— 变成字节数组,然后调用 write 方法,将 index.html 字节流写到 socket 中,那么,我们调用这两个方法,在 OS 底层发生了什么呢?我这里借鉴了一张其他文章的图片,尝试解释这个过程。

上图中,上半部分表示用户态和内核态的上下文切换。下半部分表示数据复制操作。下面说说他们的步骤:

  1. read 调用导致用户态到内核态的一次变化,同时,第一次复制开始:DMA(Direct Memory Access,直接内存存取,即不使用 CPU 拷贝数据到内存,而是 DMA 引擎传输数据到内存,用于解放 CPU) 引擎从磁盘读取 index.html 文件,并将数据放入到内核缓冲区。

  2. 发生第二次数据拷贝,即:将内核缓冲区的数据拷贝到用户缓冲区,同时,发生了一次用内核态到用户态的上下文切换。

  3. 发生第三次数据拷贝,我们调用 write 方法,系统将用户缓冲区的数据拷贝到 Socket 缓冲区。此时,又发生了一次用户态到内核态的上下文切换。

  4. 第四次拷贝,数据异步的从 Socket 缓冲区,使用 DMA 引擎拷贝到网络协议引擎。这一段,不需要进行上下文切换。

  5. write 方法返回,再次从内核态切换到用户态。

如你所见,复制拷贝操作太多了。如何优化这些流程?

mmap优化

mmap 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。如下图:

如上图,user buffer 和 kernel buffer 共享 index.html。如果你想把硬盘的 index.html 传输到网络中,再也不用拷贝到用户空间,再从用户空间拷贝到 Socket 缓冲区。

现在,你只需要从内核缓冲区拷贝到 Socket 缓冲区即可,这将减少一次内存拷贝(从 4 次变成了 3 次),但不减少上下文切换次数。

sendFile

那么,我们还能继续优化吗? Linux 2.1 版本 提供了 sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。

如上图,我们进行 sendFile 系统调用时,数据被 DMA 引擎从文件复制到内核缓冲区,然后调用 write 方法时,从内核缓冲区进入到 Socket,这时,是没有上下文切换的,因为都在内核空间。

最后,数据从 Socket 缓冲区进入到协议栈。此时,数据经过了 3 次拷贝,3 次上下文切换。那么,还能不能再继续优化呢? 例如直接从内核缓冲区拷贝到网络协议栈?

实际上,Linux 在 2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socket buffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。具体如下图:


现在,index.html 要从文件进入到网络协议栈,只需 2 次拷贝:第一次使用 DMA 引擎从文件拷贝到内核缓冲区,第二次从内核缓冲区将数据拷贝到网络协议栈;内核缓存区只会拷贝一些 offset 和 length 信息到 SocketBuffer,基本无消耗。

零拷贝的再次理解

  1. 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据,sendFile 2.1 版本实际上有 2 份数据,算不上零拷贝)。例如我们刚开始的例子,内核缓存区和 Socket 缓冲区的数据就是重复的。
  2. 零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算。

mmap和sendFile的区别

  1. mmap适合小数据量读写,sendFile适合大文件传输
  2. mmap需要4次上下文切换,3次数据拷贝;sendFile需要3次上下文切换,最少2次数据拷贝
  3. sendFile可以利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)

在选择上:rocketMQ 在消费消息时,使用了 mmap。kafka 使用了 sendFile。

NIO零拷贝案例

案例要求:

  1. 使用传统的IO方法传递一个大文件
  2. 使用NIO零拷贝方式传递一个大文件(transferTo)
  3. 看看两种传递方式耗时时间分别是多少

传统方式

package com.example.demo.netty.zerocopy;import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;public class OldIOServer {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(7001);while (true) {Socket socket = serverSocket.accept();DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());try {byte[] byteArray = new byte[4096];while (true) {int readCount = dataInputStream.read(byteArray, 0, byteArray.length);if (readCount == -1) {break;}}} catch (Exception e) {e.printStackTrace();}}}
}
package com.example.demo.netty.zerocopy;import java.io.*;
import java.net.Socket;public class OldIOClient {public static void main(String[] args) throws Exception {Socket socket = new Socket("localhost", 7001);File file = new File("bigFile.war");InputStream inputStream = new FileInputStream(file);DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());byte[] byteArray = new byte[4096];long readCount = 0;long total = 0;long startTime = System.currentTimeMillis();while ((readCount = inputStream.read(byteArray)) >= 0) {total += readCount;dataOutputStream.write(byteArray);}System.out.println("发送总字节数: " + total + ",耗时:" + (System.currentTimeMillis() - startTime));dataOutputStream.close();socket.close();inputStream.close();}
}

NIO零拷贝

package com.example.demo.netty.zerocopy;import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;public class NewIOServer {public static void main(String[] args) throws Exception {InetSocketAddress address = new InetSocketAddress(7001);ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();ServerSocket serverSocket = serverSocketChannel.socket();serverSocket.bind(address);ByteBuffer byteBuffer = ByteBuffer.allocate(4096);while (true) {SocketChannel socketChannel = serverSocketChannel.accept();int readCount = 0;while (-1 != readCount) {try {readCount = socketChannel.read(byteBuffer);} catch (Exception e) {e.printStackTrace();}byteBuffer.rewind();//倒带position=0 mark作废}}}
}
package com.example.demo.netty.zerocopy;import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;public class NewIOClient {public static void main(String[] args) throws Exception {SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("localhost", 7001));String filename = "bigFile.war";//得到一个文件channelFileChannel fileChannel = new FileInputStream(filename).getChannel();//准备发送long startTime = System.currentTimeMillis();//在linux下 一个transferTo 方法就可以完成传输//在windows下 一个transferTo只能发送8M,就需要分段传输文件,而且要主要//传输时的位置//transferTo 底层使用到零拷贝long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);System.out.println("发送总字节数: " + transferCount + ",耗时:" + (System.currentTimeMillis() - startTime));//关闭fileChannel.close();}
}

AIO基本介绍

  1. JDK7 引入了,Asynchronous I/O,即AIO ,在进行IO编程中,常用到两种模式:Reactor 和 Proactor, Java 的NIO就是Reactor,当有事件触发时,服务器端得到通知进行相应的处理
  2. AIO 即NIO2.0, 叫异步非阻塞IO。AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,他的特点是,先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
  3. 目前AIO还没有广泛应用,Netty也是基于NIO,而不是AIO,因此就不在这里讲AIO了,有兴趣的可以链接一下http://www.52im.net/thread-306-1-1.html

BIO、NIO、AIO对比

(七)Netty与零拷贝相关推荐

  1. netty的零拷贝、架构设计、ByteBuf扩容机制详解

    文章目录 1. netty高并发架构设计精髓 ①:主从.Reactor线程模型 ②:NIO多路复用非阻塞 ③:无锁串行化设计思想 ④:高可用.可扩展架构 ⑤:直接内存和零拷贝 ⑥:ByteBuf内存池 ...

  2. 【Netty】零拷贝案例 ( transferTo | transferFrom )

    文章目录 一. 案例需求 二. 传统 BIO 拷贝案例 三. 零拷贝案例 服务器端 四. 零拷贝案例 客户端 五. 零拷贝案例 运行与分析 一. 案例需求 给出两个案例 , 一个是 使用普通的 BIO ...

  3. 【Netty】零拷贝(zero-copy)

    目录 1. 零拷贝技术实现 2. 传统读取IO流的操作 2.1 读操作 2.2 写操作 2.3 MMAP+write 2.4 Sendfile 3. 零拷贝应用场景 很多更新的技术在宣传的时候,都会提 ...

  4. 【48期】盘点Netty面试常问考点:什么是 Netty 的零拷贝?

    点击上方蓝色"java大数据修炼之道", 选择"设为星标" 每晚九点: 技术干货

  5. 80-15-020-原理-零拷贝-Netty零拷贝的原理

    1.概述 Netty 的零拷贝 1. 传统意义的拷贝 是在发送数据的时候,传统的实现方式是: File.read(bytes) Socket.send(bytes) 这种方式需要四次数据拷贝和四次上下 ...

  6. 彻底搞懂Netty高性能之零拷贝

    作为Java网络编程学习者,不仅要知道NIO,还一定要学习Mina和Netty这两个优秀的网络框架.作为上一篇NIO效率高的原理之零拷贝与直接内存映射的补充,本文将针对Netty的零拷贝特性进行详细分 ...

  7. netty零拷贝之CompositeByteBuf

    目录 一. 背景简介 二. netty零拷贝 一.背景简介 在TCP网络数据传输过程中,数据包有可能被分割为独立的几个数据包进行发送,对于服务器接收端来说,单个的数据包是没有任何意义的,只有将这些数据 ...

  8. 四十七、Netty零拷贝

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

  9. 零拷贝技术在 Java 中为何这么牛?

    [CSDN 编者按]大家在学计算机的过程中是否会遇到"零拷贝"这样的字眼,本文针对这一技术为大家提供详细的概念解释,并对其产出的意义及其在Java中的应用进行说明. 责编 | 欧阳 ...

最新文章

  1. java 文件拷贝文件怎么打开_java如何拷贝文件
  2. 通过卫星图像预测区域内降雨范围和降雨量
  3. xxxx无法转换为java.lang.Class<? extends javax.validation.Payload>
  4. python制作文本编辑器_Python小实战:制作文本编辑器
  5. 飞鸽传书内部排序算法的性能比较
  6. 零日攻击的原理与防范方法
  7. (转)详解Windows Hash
  8. 删除单链表中指针q指向的结点
  9. 查看dll库导出库的函数接口
  10. 【收益管理】单资源容量控制(2)先从报童模型谈起!
  11. 三维点云课程(一)——点云基础介绍
  12. MATLAB实现imrotate函数
  13. 从零开始学习SpringCloud
  14. 如何给计算机d盘加密码,怎样给电脑文件夹加密
  15. wps2022无法加载此加载项程序mathpage.wll
  16. Python攻城师的成长——ORM(choices字段)、AJAX
  17. 程序员的可迁移技能和经验
  18. 新能源汽车比亚迪唐220V放电系统粗解,以及为什么需要支持V2G?
  19. cisco3560及二层交换机配置vlan及常用命令
  20. 我也和 chatGPT 聊了聊

热门文章

  1. Intel Core Enhanced Core架构/微架构/流水线 (9) - 执行单元发射口旁路时延
  2. Intel Core Enhanced Core架构/微架构/流水线 (2) - 代表处理器
  3. 自定义指令监听多个div_Vue 3 | 自定义指令的新玩法
  4. CentOS7环境下搭建Kafka
  5. Coding the Matrix Week 3 The Matrix 矩阵
  6. 移动终端CPU、GPU浅析
  7. [UOJ50]链式反应
  8. 【软件工程实践 · 团队项目】 第二次作业
  9. 淘宝内核月报 2017
  10. 无法将 lambda 表达式 转换为类型“System.Delegate”,因为它不是委托类型