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

简介: 零拷贝技术(Zero-Copy)是一个大家耳熟能详的技术名词了,它主要用于提升 IO(Input & Output)的传输性能。那么问题来了,为什么零拷贝技术能提升 IO 性能?## 1.零拷贝技术和性能在传统的 IO 操作中,当我们需要读取并传输数据时,我们需要在用户态(用户空间)和内核态(内核空间)中进行数据拷贝,它的执行流程如下:![](https://cdn.nlark.com/yuque/0/2024/png/92791/1706491312473-52f5904a-2742-4e99-9b78-995e9a8b9696.png?x-oss-process=image%2F

零拷贝技术(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、设计模式、消息队列等模块。

相关文章
|
11月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
10月前
|
Java 测试技术 微服务
最新技术栈下 Java 面试高频技术点实操指南详解
本指南结合最新Java技术趋势,涵盖微服务(Spring Cloud Alibaba)、响应式编程(Spring WebFlux)、容器化部署(Docker+Kubernetes)、函数式编程、性能优化及测试等核心领域。通过具体实现步骤与示例代码,深入讲解服务注册发现、配置中心、熔断限流、响应式数据库访问、JVM调优等内容。适合备战Java面试,提升实操能力,助力技术进阶。资源链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)
406 25
|
9月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
608 0
|
7月前
|
监控 前端开发 安全
Netty 高性能网络编程框架技术详解与实践指南
本文档全面介绍 Netty 高性能网络编程框架的核心概念、架构设计和实践应用。作为 Java 领域最优秀的 NIO 框架之一,Netty 提供了异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。本文将深入探讨其 Reactor 模型、ChannelPipeline、编解码器、内存管理等核心机制,帮助开发者构建高性能的网络应用系统。
497 0
|
11月前
|
SQL 存储 关系型数据库
美团面试:事务提交了,数据丢失 了 ?大概的原因是什么?
美团面试:事务提交了,数据丢失 了 ?大概的原因是什么?
美团面试:事务提交了,数据丢失 了 ?大概的原因是什么?
|
11月前
|
消息中间件 架构师 Java
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
|
10月前
|
Cloud Native Java 程序员
【2025 最新版互联网一线大厂 Java 程序员面试 + 学习指南】覆盖全面面试知识点、实用面试技巧及前沿技术实操内容
本内容涵盖互联网大厂主流技术栈的最新实操指南,包括微服务架构(Spring Cloud Alibaba Nacos、OpenFeign、Spring Cloud Gateway)、容器化与Kubernetes、云原生技术(Istio、Prometheus+Grafana)、高性能开发(Reactor响应式编程、CompletableFuture异步编程)及数据持久化(Redis分布式锁、ShardingSphere分库分表)。通过详细代码示例和操作步骤,帮助开发者掌握核心技术,适用于本地环境搭建与模块功能实践。适合Java程序员学习和面试准备,附带资源链接供深入研究。
271 5
|
12月前
|
人工智能 算法 数据库
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
|
人工智能 自然语言处理 算法
通义灵码助力技术求职:如何成为笔试面试冲刺的“超级助手”
在技术岗位竞争日益激烈的当下,求职季的备战已不仅是知识储备的较量,更是效率与实战能力的比拼。面对海量面试题、复杂算法挑战及快速迭代的技术框架,开发者亟需高效工具辅助突破瓶颈。阿里云推出的智能编码工具通义灵码,凭借其代码生成、优化及智能问答等核心能力,正成为开发者备战求职季的“超级助手”。
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
下一篇
开通oss服务