什么是零拷贝

简介: 学习Netty时 看到Netty高性能的原因之一是使用零拷贝技术学习Kafka时 看到其高性能的原因之一也使用了零拷贝技术那么到底什么是零拷贝?本文简单做个描述。

学习Netty时 看到Netty高性能的原因之一是使用零拷贝技术

学习Kafka时 看到其高性能的原因之一也使用了零拷贝技术

那么到底什么是零拷贝?本文简单做个描述


先解释几个概念


  • 用户态:只能受限地访问内存,不允许访问外围设备。占用 CPU 的能力被剥夺,CPU资源可以被其他程序获取。
  • 内核态:CPU可以访问内存中所有的数据,包括外围设备,例如硬盘、网卡,CPU也可以将自己从一个程序切换到另一个程序。
  • DMA (direct memory access):直接内存访问,是一种不经过CPU而直接从内存存取数据的数据交换模式。在DMA模式下,CPU只须向DMA控制器下达指令,让DMA控制器来处理数据的传送,数据传送完毕再把信息反馈给CPU,这样就很大程度上减轻了CPU资源占有率,可以大大节省系统资源



传统的数据拷贝方法


    下图为传统的数据拷贝方法:


13.jpg


上图分别对应传统 I/O 操作的数据读写流程,整个过程涉及 4 次 拷贝,


  • 1 .数据从磁盘读取到内核的read buffer
  • 2. 数据从内核缓冲区拷贝到用户缓冲区
  • 3. 数据从用户缓冲区拷贝到内核的socket buffer
  • 4. 数据从内核的socket buffer拷贝到网卡接口的缓冲区


       并且在用户态和内核态中间进行了2次切换,无疑也加重了CPU负担。

       在此过程中,我们没有对文件内容做任何修改,那么在内核空间和用户空间来回拷贝数据无疑就是一种浪费,而零拷贝主要就是为了解决这种低效性。


解决方案


      一个很明显的着力点就是减少数据在内核空间和用户空间来回拷贝。

       Linux能够做到在数据传输的过程中,避免数据在操作系统内核态buffer和用户态buffer之间进行复制。Linux中提供类似的系统调用函数主要有mmap()、sendfile()及splice()。下面介绍其中两种。


  • mmap

        我们减少拷贝次数的一种方法是调用mmap()来代替read调用:


buf = mmap(diskfd, len);write(sockfd, buf, len);


       应用程序调用mmap(),磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核态,最后,socket缓冲区再把数据发到网卡去


14.jpg


使用mmap替代read很明显减少了一次拷贝,当拷贝数据量很大时,无疑提升了效率。


  • sendfile
    从2.1版内核开始,Linux引入了sendfile来简化操作。


#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);



  系统调用sendfile()在代表输入文件的描述符in_fd和代表输出文件的描述符out_fd之间传送文件内容(字节)。描述符out_fd必须指向一个套接字,而in_fd指向的文件必须是可以mmap的。这些局限限制了sendfile的使用,使sendfile只能将数据从文件传递到套接字上,反之则不行。


       使用sendfile不仅减少了数据拷贝的次数,还减少了上下文切换,数据传送始终只发生在kernel space。


15.jpg


方案对比


       限于篇幅原因并没有把所有的零拷贝方式都介绍完整,以下是Linux中各方式的对比


拷贝方式 CPU拷贝 DMA拷贝 系统调用 上下文切换
传统方式(read + write) 2 2 read / write 4
内存映射(mmap + write) 1 2 mmap / write 4
sendfile 1 2 sendfile 2
sendfile + DMA gather copy 0 2 sendfile 2
splice 0 2 splice 2


        Kafka采用的是Linux系统的函数sendfile(),允许操作系统将数据从Page Cache直接发送到网络,以此来避免数据复制。


       Netty 中的零拷贝和上面提到的操作系统层面上的零拷贝不太一样, 我们所说的 Netty 零拷贝完全是基于(Java 层面)用户态的,它的更多的是偏向于数据操作优化这样的概念,具体表现在以下几个方面:


  • Netty 通过 DefaultFileRegion 类对 java.nio.channels.FileChannel 的 tranferTo() 方法进行包装,在文件传输时可以将文件缓冲区的数据直接发送到目的通道(Channel)
  • ByteBuf 可以通过 wrap 操作把字节数组、ByteBuf、ByteBuffer 包装成一个 ByteBuf 对象, 进而避免了拷贝操作
  • ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf,避免了内存的拷贝
  • Netty 提供了 CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免了各个 ByteBuf 之间的拷贝


17.png

相关文章
|
消息中间件 存储 Java
kafka零拷贝
kafka零拷贝
225 0
|
2月前
|
数据采集 监控 BI
RPA与爬虫的本质区别:企业自动化如何选对工具?
RPA与网络爬虫虽同属自动化技术,但定位迥异。RPA模拟人工操作,实现跨系统流程自动化,适用于企业内部业务处理;爬虫则专注网页数据采集,面临合规挑战。企业应根据操作场景与数据来源合理选用。
412 0
|
8月前
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
334 12
|
9月前
|
消息中间件 缓存 监控
go高并发之路——消息中间件kafka
本文介绍了高并发业务中的流量高峰应对措施,重点讲解了Kafka消息中间件的使用,包括常用的Go语言库sarama及其版本问题,以及Kafka的版本选择建议。文中还详细解释了Kafka生产者的四种分区策略:轮询、随机、按Key和指定分区,并提供了相应的代码示例。
227 1
go高并发之路——消息中间件kafka
|
10月前
|
Java API 调度
Java 线程的生命周期
在JDK 1.5之前,线程的生命周期包括五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。JDK 1.5及之后增加了三种阻塞状态,共六种状态:新建、可运行、终止、锁阻塞、计时等待和无限等待。这些状态描述了线程在操作系统和JVM中的不同阶段。
276 4
Java 线程的生命周期
|
10月前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
11月前
|
消息中间件 Java Linux
得物面试:什么是零复制?说说 零复制 底层原理?(吊打面试官)
尼恩,40岁老架构师,专注于技术分享与面试辅导。近期,尼恩的读者群中有小伙伴在面试一线互联网企业如得物、阿里、滴滴等时,遇到了关于零复制技术的重要问题。为此,尼恩系统化地整理了零复制的底层原理,包括RocketMQ和Kafka的零复制实现,以及DMA、mmap、sendfile等技术的应用。尼恩还计划推出一系列文章,深入探讨Netty、Kafka、RocketMQ等框架的零复制技术,帮助大家在面试中脱颖而出,顺利拿到高薪Offer。此外,尼恩还提供了《尼恩Java面试宝典》PDF等资源,助力大家提升技术水平。更多内容请关注尼恩的公众号【技术自由圈】。
得物面试:什么是零复制?说说 零复制 底层原理?(吊打面试官)
|
11月前
|
存储 NoSQL 应用服务中间件
Redis 学习笔记
本文详细介绍了Redis的基本概念、特性、应用场景,并提供了在Centos下安装Redis5的步骤,以及如何使用Redis客户端和命令进行数据类型操作和管理。
416 2
|
Linux
Linux内核中USB设备驱动实现
Linux内核中USB设备驱动实现
215 0
|
消息中间件 缓存 运维
中间件数据一致性和可靠性问题
【7月更文挑战第14天】
181 1
中间件数据一致性和可靠性问题