美团面试: 说说Netty的零拷贝技术?

磊哥课程2024-06-07 14:22:55  68

零拷贝技术(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 种实现:

使用堆外内存:避免 JVM 堆内存到堆外内存的数据拷贝,从而提升了 IO 的操作性能。

使用 CompositeByteBuf 合并对象:可以组合多个 Buffer 对象合并成一个逻辑上的对象,避免通过传统内存拷贝的方式将几个 Buffer 合并成一个大的 Buffer。

通过 Unpooled.wrappedBuffer 合并数据:可以将 byte 数组包装成 ByteBuf 对象,包装过程中不会产生内存拷贝。

使用 ByteBuf.slice 共享对象:操作与 Unpooled.wrappedBuffer 相反,slice 操作可以将一个 ByteBuf 对象切分成多个 ByteBuf 对象,切分过程中不会产生内存拷贝,底层共享一个 byte 数组的存储空间。

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

它们的具体实现如下。

3.1 使用堆外内存

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

操作系统并不感知 JVM 的堆内存,而且 JVM 的内存布局与操作系统所分配的是不一样的,操作系统并不会按照 JVM 的行为来读写数据。

同一个对象的内存地址随着 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 的使用案例:

@Overridepublic 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 + ' '); return; } finally { if (length < 0 && raf != null) { raf.close; } } ctx.write("OK: " + raf.length + ' '); 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(" ");}

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

课后思考

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

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

转载此文是出于传递更多信息目的。若来源标注错误或侵犯了您的合法权益,请与本站联系,我们将及时更正、删除、谢谢。
https://www.414w.com/read/684963.html
0
随机主题
三大消息:中方罕见打破沉默!“逮捕以总理”突响;美国紧急发声烂番茄93%, 有人狂骂, 有人狂爱, 信谁?树挪死人挪活! 米兰废柴48场造23球助真蓝黑夺冠 上赛季40场0球售价20.08万起的沃尔沃EX30, 410公里的续航有多少人买账?战斗力不输大S! 马筱梅深得汪小菲真传, 痛骂前任: 蹭热度的蛆虫特高压多条储备项目披露, 电网行业景气度有望持续!重庆渝中区: 南纪门街道创新服务模式 “三快三及时”让“民呼我为”有精度更有温度关注: 钟渭平率商用车企业访问日本三井住友总部大S拿孩子赌气? 儿子被学校劝退本人不接电话, 汪小菲无奈出面生涯第4冠, 36岁韩德君退役? 谁注意郭艾伦喊话, 大韩举动感人美股收评: 三大指数集体下跌, 太阳能、通讯设备板块走高, 金属、采矿、油气股跌幅居前为什么很多人讨厌国漫的超前点播, 但又希望快点出, 原因有两个DNF: 新副本传奇页游风, 记录室钥匙千万留好! 每周7次奖励拉满不是首钢, 郭艾伦官宣下家, 李晓旭跟腱断裂, 辽篮奖金排名曝光阿隆索服输: 对手逼抢太厉害了, 我们无法应对2024年618值得入手的1000-2000元区间数码好物推荐浏览器主页被篡改了怎么办?一招教你解决它贪财又贪色! 德不配位的4位老戏骨, “晚节不保”真的一点都不冤中国制造又杀回第一!再次超过美国,成印度第一大贸易伙伴与预售价一样 2.4T乘用炮、商用炮上市 12.58万元起售姚振华及宝能等新增2则被执行人信息, 执行标的合计22.88亿
最新回复(0)