零拷贝技术(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. DBJ,DB,CJJ,CECS 标准区别及全套下载教程

    DBJ DBJ开头的标准是地方建筑标准:D--地方. B--标准. J--建筑. <中华人民共和国标准化法>将中国标准分为国家标准.行业标准.地方标准(DB).企业标准(Q/)四级.地方标 ...

  2. 力扣534(MySQL)-游戏玩法分析Ⅲ(中等)

    题目: 需求:请编写一个 SQL 查询,同时报告每组玩家和日期,以及玩家到目前为止玩了多少游戏.也就是说,在此日期之前玩家所玩的游戏总数.详细情况请查看示例. 查询结果格式在以下示例中: 对于 ID ...

  3. 力扣682(java)-棒球比赛(简单)

    题目: 你现在是一场采用特殊赛制棒球比赛的记录员.这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分. 比赛开始时,记录是空白的.你会得到一个记录操作的字符串列表 ops,其中 ops ...

  4. 2021年阿里云年中钜惠攻略,注册即可抽 iPhone 12 Pro 等好礼

    简介: 七月流火,燃情盛夏!值此季节,阿里云又推出了年中钜惠,精选百款产品,助力创业新势力.从7月26日开始,每天上午10点.下午4点将会放出爆款产品,进行限量秒杀,大家不要错过.注册登陆还可抽取 i ...

  5. [FAQ] Goland 始终没有包代码的提示 ?

    表现:import 引入的包始终是红色的,表示没有找到引入的包. 注意,在这里开启Go Modules: 然后在 Exteneral Libraries 里看到 Go Modules 即可. Refe ...

  6. 修复 GitLab 的 CI Runner 提示找不到 pwsh 执行文件

    本文告诉大家如何修复使用 GitLab 的 Runner 做 CI 时提示 "pwsh": executable file not found in %PATH% 错误 有两个方法 ...

  7. 2019-9-27-微软的-P2P-下载方式

    title author date CreateTime categories 微软的 P2P 下载方式 lindexi 2019-09-27 09:44:44 +0800 2019-09-27 09 ...

  8. 2019-8-31-dotnet-core-使用-PowerShell-脚本

    title author date CreateTime categories dotnet core 使用 PowerShell 脚本 lindexi 2019-08-31 16:55:58 +08 ...

  9. dotnet build error CS5001: Program does not contain a static 'Main' method suitable for an entry point

    前言 Docker环境编译.Net6项目,出现诡异的CS5001 Program does not contain a static 'Main' method suitable for an ent ...

  10. docker-compose 安装 mysql:5.7.31

    目录 一.新建一个启动服务的目录 二.新建文件docker-compose.yml 三.新建角本文件 init-mysql.sh 四.实使化目录和配置文件 启动服务 登陆mysql 其它操作 参考文档 ...