零拷贝技术(Zero-Copy)是一个大家耳熟能详的技术名词了,它主要用于提升 IO(Input & Output)的传输性能。

那么问题来了,为什么零拷贝技术能提升 IO 性能?

1.零拷贝技术和性能

在传统的 IO 操作中,当我们需要读取并传输数据时,我们需要在用户态(用户空间)和内核态(内核空间)中进行数据拷贝,它的执行流程如下:



从上述流程我们可以看出,在传统的 IO 操作中,我们是需要 4 次拷贝和 4 次上下文切换(用户态和内核态的切换)的。

而每次数据拷贝和上下文切换都有时间成本,会让程序的执行时间变成,所以零拷贝技术的出现就是为了减少数据的拷贝次数以及上下文的切换次数的。

1.1 什么是用户态和内核态?

操作系统有用户态和内核态之分,这是因为计算机体系结构中的操作系统设计了两个不同的执行环境,以提供不同的功能和特权级别。

  • 用户态(User Mode)是指应用程序运行时的执行环境。在用户态下,应用程序只能访问受限资源,如应用程序自身的内存空间、CPU 寄存器等,并且不能直接访问操作系统的底层资源和硬件设备。
  • 内核态(Kernel Mode)是指操作系统内核运行时的执行环境。在内核态下,操作系统具有更高的权限,可以直接访问系统的硬件和底层资源,如 CPU、内存、设备驱动程序等。

1.2 什么是DMA?

DMA(Direct Memory Access,直接内存访问)技术,绕过 CPU,直接在内存和外设之间进行数据传输。这样可以减少 CPU 的参与,提高数据传输的效率。

2.Linux零拷贝技术

Linux 下实现零拷贝的主要实现技术是 MMap、sendFile,它们的具体介绍如下。

2.1 MMap

MMap(Memory Map)是 Linux 操作系统中提供的一种将文件映射到进程地址空间的一种机制,通过 MMap 进程可以像访问内存一样访问文件,而无需显式的复制操作。

使用 MMap 可以把 IO 执行流程优化成以下执行步骤:



传统的 IO 需要四次拷贝和四次上下文(用户态和内核态)切换,而 MMap 只需要三次拷贝和四次上下文切换,从而能够提升程序整体的执行效率,并且节省了程序的内存空间。

2.2 senFile 方法

在 Linux 操作系统中 sendFile() 是一个系统调用函数,用于高效地将文件数据从内核空间直接传输到网络套接字(Socket)上,从而实现零拷贝技术。这个函数的主要目的是减少 CPU 上下文切换以及内存复制操作,提高文件传输性能。

使用 sendFile() 可以把 IO 执行流程优化成以下执行步骤:

3.Netty零拷贝技术

Netty 中的零拷贝和传统 Linux 的零拷贝技术的实现不太一样,Netty 中的零拷贝技术主要是通过优化用户态的操作来提升 IO 的执行速度,从而实现零拷贝的

PS:所有可以提升 IO 执行效率的操作或手段都可以称之为零拷贝技术。

Netty 中的零拷贝技术主要有以下 5 种实现:

  1. 使用堆外内存:避免 JVM 堆内存到堆外内存的数据拷贝,从而提升了 IO 的操作性能。
  2. 使用 CompositeByteBuf 合并对象:可以组合多个 Buffer 对象合并成一个逻辑上的对象,避免通过传统内存拷贝的方式将几个 Buffer 合并成一个大的 Buffer。
  3. 通过 Unpooled.wrappedBuffer 合并数据:可以将 byte 数组包装成 ByteBuf 对象,包装过程中不会产生内存拷贝。
  4. 使用 ByteBuf.slice 共享对象:操作与 Unpooled.wrappedBuffer 相反,slice 操作可以将一个 ByteBuf 对象切分成多个 ByteBuf 对象,切分过程中不会产生内存拷贝,底层共享一个 byte 数组的存储空间。
  5. 使用 FileRegion 实现零拷贝:FileRegion 底层封装了 FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝,这属于操作系统级别的零拷贝。

它们的具体实现如下。

3.1 使用堆外内存

正常情况下,JVM 需要将数据从 JVM 堆内存拷贝到堆外内存进行业务执行的,这是因为:

  1. 操作系统并不感知 JVM 的堆内存,而且 JVM 的内存布局与操作系统所分配的是不一样的,操作系统并不会按照 JVM 的行为来读写数据。
  2. 同一个对象的内存地址随着 JVM GC 的执行可能会随时发生变化,例如 JVM GC 的过程中会通过压缩来减少内存碎片,这就涉及对象移动的问题了。

而 Netty 在进行 I/O 操作时都是使用的堆外内存,可以避免数据从 JVM 堆内存到堆外内存的拷贝。

3.2 使用CompositeByteBuf合并对象

CompositeByteBuf 可以理解为一个虚拟的 Buffer 对象,它是由多个 ByteBuf 组合而成,但是在 CompositeByteBuf 内部保存着每个 ByteBuf 的引用关系,从逻辑上构成一个整体。使用 CompositeByteBuf 我们可以合并两个 ByteBuf 对象,从而避免两个对象合并时需要两次 CPU 拷贝操作的问题,在没有使用 CompositeByteBuf 时,我们的操作是这样的:

ByteBuf httpBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());
httpBuf.writeBytes(header);
httpBuf.writeBytes(body);

而实现 header 和 body 这两个 ByteBuf 的合并,需要先初始化一个新的 httpBuf,然后再将 header 和 body 分别拷贝到新的 httpBuf。合并过程中涉及两次 CPU 拷贝,这非常浪费性能,所以我们就可以使用 CompositeByteBuf 了,它的使用如下:

CompositeByteBuf httpBuf = Unpooled.compositeBuffer();
httpBuf.addComponents(true, header, body);

CompositeByteBuf 通过调用 addComponents() 方法来添加多个 ByteBuf,但是底层的 byte 数组是复用的,不会发生内存拷贝。

3.3 通过Unpooled.wrappedBuffer合并数据

Unpooled.wrappedBuffer 的操作类似,使用它可以将不同的数据源的一个或者多个数据包装成一个大的 ByteBuf 对象,其中数据源的类型包括 byte[]、ByteBuf、ByteBuffer。包装的过程中不会发生数据拷贝操作,包装后生成的 ByteBuf 对象和原始 ByteBuf 对象是共享底层的 byte 数组。

3.4 使用 ByteBuf.slice 共享对象

ByteBuf.slice 和 Unpooled.wrappedBuffer 的逻辑正好相反,ByteBuf.slice 是将一个 ByteBuf 对象切分成多个共享同一个底层存储的 ByteBuf 对象,从而避免对象分割时的数据拷贝,它的使用如下:

ByteBuf httpBuf = ...
ByteBuf header = httpBuf.slice(0, 6);
ByteBuf body = httpBuf.slice(6, 4);

3.5 使用 FileRegion 实现文件零拷贝

FileRegion 底层封装了 FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝,这属于操作系统级别的零拷贝。

以下是 FileRegion 的默认实现类 DefaultFileRegion 的使用案例:

@Override
public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
RandomAccessFile raf = null;
long length = -1;
try {
raf = new RandomAccessFile(msg, "r");
length = raf.length();
} catch (Exception e) {
ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + '\n');
return;
} finally {
if (length < 0 && raf != null) {
raf.close();
}
}
ctx.write("OK: " + raf.length() + '\n');
if (ctx.pipeline().get(SslHandler.class) == null) {
// SSL not enabled - can use zero-copy file transfer.
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));
} else {
// SSL enabled - cannot use zero-copy file transfer.
ctx.write(new ChunkedFile(raf));
}
ctx.writeAndFlush("\n"); }

从上述代码可以看出,可以通过 DefaultFileRegion 将文件内容直接写入到 NioSocketChannel 中,从而避免了内核缓冲区和用户态缓冲区之间的数据拷贝。

课后思考

那么问题来了,FileRegion 是如何实现零拷贝的呢?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

美团面试:说说Netty的零拷贝技术?的更多相关文章

  1. 【Netty技术专题】「原理分析系列」Netty强大特性之ByteBuf零拷贝技术原理分析

    零拷贝Zero-Copy 我们先来看下它的定义: "Zero-copy" describes computer operations in which the CPU does n ...

  2. [软件体系结构/架构]零拷贝技术(Zero-copy)[转发]

    0 前言 近期遇到难题:1个大数据集的查询导出API,因从数据库查询后占用内存极大,每次调用将消耗近100MB的JVM内存资源.故现需考虑研究和应用零拷贝技术. 如下全文摘自: 看一遍就理解:零拷贝原 ...

  3. Linux 中的零拷贝技术,第 2 部分

    技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概 ...

  4. Linux 中的零拷贝技术,第 1 部分

    概述 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关 ...

  5. Linux 零拷贝技术

    简介 零拷贝(zero-copy)技术可以减少数据拷贝和共享总线操作的次数,消除通信数据在存储器之间不必要的中间拷贝过程,有效地提高通信效率,是设计高速接口通道.实现高速服务器和路由器的关键技术之一. ...

  6. Linux中的零拷贝技术

    转载:https://www.jianshu.com/p/fad3339e3448 引文## 在写一个服务端程序时(Web Server或者文件服务器),文件下载是一个基本功能.这时候服务端的任务是: ...

  7. Java基础-零拷贝技术应用案例

    Java基础-零拷贝技术应用案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 零拷贝技术在Hadoop生态圈中很多组件得到应用,典型的比如kafka组件,它就很成功的应用了零拷贝 ...

  8. Linux零拷贝技术,看完这篇文章就懂了

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,欢迎大家关注,二维码文末可以扫. 本文讲解 ...

  9. Linux零拷贝技术 直接 io

    Linux零拷贝技术 .https://kknews.cc/code/2yeazxe.html   https://zhuanlan.zhihu.com/p/76640160 https://clou ...

  10. 【转】浅析Linux中的零拷贝技术

    本文探讨Linux中主要的几种零拷贝技术以及零拷贝技术适用的场景.为了迅速建立起零拷贝的概念,我们拿一个常用的场景进行引入: 引文## 在写一个服务端程序时(Web Server或者文件服务器),文件 ...

随机推荐

  1. Exception in thread "main" java.util.NoSuchElementException

    Exception in thread "main" java.util.NoSuchElementException 用了两个scanner的时候,执行程序发生报错:Except ...

  2. 深度解密|基于 eBPF 的 Kubernetes 问题排查全景图发布

    ​简介:通过 eBPF 无侵入地采集多语言.多网络协议的黄金指标/网络指标/Trace,通过关联 Kubernetes 对象.应用.云服务等各种上下文,同时在需要进一步下钻的时候提供专业化的监测工具( ...

  3. 基于 KubeVela 与 Kubernetes 打造“无限能力”的开放 PaaS

    简介: 本文整理自阿里云容器技术专家.OAM 规范主要制定者之一.KubeVela 作者和负责人孙健波(天元)在阿里云开发者社区"周二开源日"的直播分享,将剖析当前 Kuberne ...

  4. 阿里 & 蚂蚁自研 IDE 研发框架 OpenSumi 正式开源

    ​简介:经历近 3 年时间,在阿里集团及蚂蚁集团共建小组的努力下,OpenSumi 作为国内首个强定制性.高性能,兼容 VS Code 插件体系的 IDE 研发框架,今天正式对外开源. ​ 作者 | ...

  5. 形式化验证工具TLA+:程序员视角的入门之道

    ​简介: 女娲是飞天分布式系统中提供分布式协同的基础服务,支撑着阿里云的计算.网络.存储等几乎所有云产品.在女娲分布式协同服务中,一致性引擎是核心基础模块,支持了Paxos,Raft,EPaxos等多 ...

  6. DataWorks功能实践速览 — 参数透传

    ​简介: DataWorks功能实践系列,帮助您解析业务实现过程中的痛点,提高业务功能使用效率! ​ 往期回顾: DataWorks 功能实践速览01期--数据同步解决方案:为您介绍不同场景下可选的数 ...

  7. [GPT] gradio-chatbot 原理及代码解析

      GradioChatBot 是一个基于 Gradio 的聊天机器人,它可以与不同的 URL 进行对话.其原理是通过将用户输入的文本发送到指定的 URL,然后接收并解析 URL 返回的响应,然后将响 ...

  8. 2018-8-29-dotnet-core-添加-SublimeText-编译插件

    title author date CreateTime categories dotnet core 添加 SublimeText 编译插件 lindexi 2018-08-29 08:53:47 ...

  9. 五:瑞芯微RV1109

    瑞芯微RV1109是一款用于工控机或人工智能视觉应用的高性能机器视觉处理器SoC. 参考资料 http://www.neardi.com/news_22/434.html https://www.ro ...

  10. LVS负载均衡(2)-- NAT模型搭建实例

    目录 1. LVS NAT模型搭建 1.1 NAT模型网络规划 1.2 NAT模型访问流程 1.3 NAT模型配置步骤 1.3.1 ROUTER设备配置 1.3.2 后端nginx服务器配置 1.3. ...