如何高效实现文件传输:小文件采用零拷贝、大文件采用异步io+直接io

一般会如何实现文件传输?

服务器提供文件传输功能,需要将磁盘上的文件读取出来,通过网络协议发送到客户端。如果需要你自己编码实现这个文件传输功能,你会怎么实现呢?

通常,你会选择最直接的方法:从网络请求中找出文件在磁盘中的路径后,如果这个文件比较大,假设有 320MB,可以在内存中分配 32KB 的缓冲区,再把文件分成一万份,每份只有 32KB,这样,从文件的起始位置读入 32KB 到缓冲区,再通过网络 API 把这 32KB 发送到客户端。接着重复一万次,直到把完整的文件都发送完毕。如下图所示:

​不过这个方案性能并不好,主要有两个原因。

上下文切换:

首先,它至少经历了 4 万次用户态与内核态的上下文切换。因为每处理 32KB 的消息,就需要一次 read 调用和一次 write 调用,每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。可见,每处理 32KB,就有 4 次上下文切换,重复 1 万次后就有 4 万次切换。

上下文切换的成本并不小,虽然一次切换仅消耗几十纳秒到几微秒,但高并发服务会放大这类时间的消耗。

内存拷贝:

其次,这个方案做了 4 万次内存拷贝,对 320MB 文件拷贝的字节数也翻了 4 倍,到了 1280MB。很显然,过多的内存拷贝无谓地消耗了 CPU 资源,降低了系统的并发处理能力。

所以要想提升传输文件的性能,需要从降低上下文切换的频率和内存拷贝次数两个方向入手。

零拷贝如何提升文件传输性能?

首先,我们来看如何降低上下文切换的频率。

为什么读取磁盘文件时,一定要做上下文切换呢?这是因为,读取磁盘或者操作网卡都由操作系统内核完成。内核负责管理系统上的所有进程,它的权限最高,工作环境与用户进程完全不同。只要我们的代码执行 read 或者 write 这样的系统调用,一定会发生 2 次上下文切换:首先从用户态切换到内核态,当内核执行完任务后,再切换回用户态交由进程代码执行。

因此,如果想减少上下文切换次数,就一定要减少系统调用的次数。解决方案就是把 read、write 两次系统调用合并成一次,在内核中完成磁盘与网卡的数据交换。

其次,我们应该考虑如何减少内存拷贝次数。

每周期中的 4 次内存拷贝,其中与物理设备相关的 2 次拷贝是必不可少的,包括:把磁盘内容拷贝到内存,以及把内存拷贝到网卡。但另外 2 次与用户缓冲区相关的拷贝动作都不是必需的,因为在把磁盘文件发到网络的场景中,用户缓冲区没有必须存在的理由。

如果内核在读取文件后,直接把 PageCache 中的内容拷贝到 Socket 缓冲区,待到网卡发送完毕后,再通知进程,这样就只有 2 次上下文切换,和 3 次内存拷贝。

​如果网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术,还可以再去除 Socket 缓冲区的拷贝,这样一共只有 2 次内存拷贝。

​实际上,这就是零拷贝技术。

相关视频推荐

手写用户态协议栈以及零拷贝的实现

服务器性能优化,异步处理有哪些不一样的

用户态网络缓冲区设计-ringbuffer、chainbuffer

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

​它是操作系统提供的新函数,同时接收文件描述符和 TCP socket 作为输入参数,这样执行时就可以不需要用户层缓存,完全在内核态完成内存拷贝,既减少了内存拷贝次数,也降低了上下文切换次数。

而且,零拷贝取消了用户缓冲区后,不只降低了用户内存的消耗,还通过最大化利用 socket 缓冲区中的内存,间接地再一次减少了系统调用的次数,从而带来了大幅减少上下文切换次数的机会!

你可以回忆下,没用零拷贝时,为了传输 320MB 的文件,在用户缓冲区分配了 32KB 的内存,把文件分成 1 万份传送,然而,这 32KB 是怎么来的?为什么不是 32MB 或者 32 字节呢?这是因为,在没有零拷贝的情况下,我们希望内存的利用率最高。如果用户缓冲区过大,它就无法一次性把消息全拷贝给 socket 缓冲区;如果用户缓冲区过小,则会导致过多的 read/write 系统调用。

那用户缓冲区为什么不与 socket 缓冲区大小一致呢?这是因为,socket 缓冲区的可用空间是动态变化的,它既用于 TCP 滑动窗口,也用于应用缓冲区,还受到整个系统内存的影响。尤其在长肥网络中,它的变化范围特别大。

零拷贝使我们不必关心 socket 缓冲区的大小。比如,调用零拷贝发送方法时,尽可以把发送字节数设为文件的所有未发送字节数,例如 320MB,也许此时 socket 缓冲区大小为 1.4MB,那么一次性就会发送 1.4MB 到客户端,而不是只有 32KB。这意味着对于 1.4MB 的 1 次零拷贝,仅带来 2 次上下文切换,而不使用零拷贝且用户缓冲区为 32KB 时,经历了 176 次(4 * 1.4MB/32KB)上下文切换。

综合上述各种优点,零拷贝可以把性能提升至少一倍以上!对文章开头提到的 320MB 文件的传输,当 socket 缓冲区在 1.4MB 左右时,只需要 4 百多次上下文切换,以及 4 百多次内存拷贝,拷贝的数据量也仅有 640MB,这样,不只请求时延会降低,处理每个请求消耗的 CPU 资源也会更少,从而支持更多的并发请求。

此外,零拷贝还使用了 PageCache 技术,通过它,零拷贝可以进一步提升性能,我们接下来看看 PageCache 是如何做到这一点的。

PageCache,磁盘高速缓存

回顾上文中的几张图,你会发现,读取文件时,是先把磁盘文件拷贝到 PageCache 上,再拷贝到进程中。为什么这样做呢?有两个原因所致。

第一,由于磁盘比内存的速度慢许多,所以我们应该想办法把读写磁盘替换成读写内存,比如把磁盘中的数据复制到内存中,就可以用读内存替换读磁盘。但是,内存空间远比磁盘要小,内存中注定只能复制一小部分磁盘中的数据。

选择哪些数据复制到内存呢?通常,刚被访问的数据在短时间内再次被访问的概率很高(这也叫“时间局部性”原理),用 PageCache 缓存最近访问的数据,当空间不足时淘汰最久未被访问的缓存(即 LRU 算法)。读磁盘时优先到 PageCache 中找一找,如果数据存在便直接返回,这便大大提升了读磁盘的性能。

第二,读取磁盘数据时,需要先找到数据所在的位置,对于机械磁盘来说,就是旋转磁头到数据所在的扇区,再开始顺序读取数据。其中,旋转磁头耗时很长,为了降低它的影响,PageCache 使用了预读功能。

也就是说,虽然 read 方法只读取了 0-32KB 的字节,但内核会把其后的 32-64KB 也读取到 PageCache,这后 32KB 读取的成本很低。如果在 32-64KB 淘汰出 PageCache 前,进程读取到它了,收益就非常大。这一讲的传输文件场景中这是必然发生的。

从这两点可以看到 PageCache 的优点,它在 90% 以上场景下都会提升磁盘性能,但在某些情况下,PageCache 会不起作用,甚至由于多做了一次内存拷贝,造成性能的降低。在这些场景中,使用了 PageCache 的零拷贝也会损失性能。

具体是什么场景呢?就是在传输大文件的时候。比如,你有很多 GB 级的文件需要传输,每当用户访问这些大文件时,内核就会把它们载入到 PageCache 中,这些大文件很快会把有限的 PageCache 占满。

然而,由于文件太大,文件中某一部分内容被再次访问到的概率其实非常低。这带来了 2 个问题:首先,由于 PageCache 长期被大文件占据,热点小文件就无法充分使用 PageCache,它们读起来变慢了;其次,PageCache 中的大文件没有享受到缓存的好处,但却耗费 CPU 多拷贝到 PageCache 一次。

所以,高并发场景下,为了防止 PageCache 被大文件占满后不再对小文件产生作用,大文件不应使用 PageCache,进而也不应使用零拷贝技术处理。

异步 IO + 直接 IO

高并发场景处理大文件时,应当使用异步 IO 和直接 IO 来替换零拷贝技术。

仍然回到本讲开头的例子,当调用 read 方法读取文件时,实际上 read 方法会在磁盘寻址过程中阻塞等待,导致进程无法并发地处理其他任务,如下图所示:

​异步 IO(异步 IO 既可以处理网络 IO,也可以处理磁盘 IO,这里我们只关注磁盘 IO)可以解决阻塞问题。它把读操作分为两部分,前半部分向内核发起读请求,但不等待数据就位就立刻返回,此时进程可以并发地处理其他任务。当内核将磁盘中的数据拷贝到进程缓冲区后,进程将接收到内核的通知,再去处理数据,这是异步 IO 的后半部分。如下图所示:

​从图中可以看到,异步 IO 并没有拷贝到 PageCache 中,这其实是异步 IO 实现上的缺陷。经过 PageCache 的 IO 我们称为缓存 IO,它与虚拟内存系统耦合太紧,导致异步 IO 从诞生起到现在都不支持缓存 IO。

绕过 PageCache 的 IO 是个新物种,我们把它称为直接 IO。对于磁盘,异步 IO 只支持直接 IO。

直接 IO 的应用场景并不多,主要有两种:第一,应用程序已经实现了磁盘文件的缓存,不需要 PageCache 再次缓存,引发额外的性能消耗。比如 MySQL 等数据库就使用直接 IO;第二,高并发下传输大文件,我们上文提到过,大文件难以命中 PageCache 缓存,又带来额外的内存拷贝,同时还挤占了小文件使用 PageCache 时需要的内存,因此,这时应该使用直接 IO。

当然,直接 IO 也有一定的缺点。除了缓存外,内核(IO 调度算法)会试图缓存尽量多的连续 IO 在 PageCache 中,最后合并成一个更大的 IO 再发给磁盘,这样可以减少磁盘的寻址操作;另外,内核也会预读后续的 IO 放在 PageCache 中,减少磁盘操作。直接 IO 绕过了 PageCache,所以无法享受这些性能提升。

有了直接 IO 后,异步 IO 就可以无阻塞地读取文件了。现在,大文件由异步 IO 和直接 IO 处理,小文件则交由零拷贝处理,至于判断文件大小的阈值可以灵活配置(参见 Nginx 的 directio 指令)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/52124.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QT基于TCP协议实现数据传输以及波形绘制——安卓APP及Windows程序双版本

文章代码有非常非常之详细的解析!!!诸位可放心食用 这个玩意我做了两个,一个是安卓app,一个是Windows程序。代码并非全部都是由我从无到有实现,只是实现了我想要的功能。多亏了巨人的肩膀,开源…

小研究 - 一种复杂微服务系统异常行为分析与定位算法(一)

针对极端学生化偏差(Extreme Studentized &#…

Java中的生产者/消费者模型

一、什么是生产者/消费者模型 生产者-消费者模型(Producer-Consumer problem)是一个非常经典的多线程并发协作的模型。 比如某个模块负责生产数据,而另一个模块负责处理数据。产生数据的模块就形象地被称为生产者;而处理数据的模…

后端一次返回大量数据,前端做分页处理

问题描述&#xff1a;后端接口返回大量数据&#xff0c;没有做分页处理&#xff0c;不支持传参pageNum&#xff0c;pageSize 本文为转载文章&#xff0c;原文章&#xff1a;后端一次返回大量数据&#xff0c;前端做分页处理 1.template中 分页 <el-paginationsize-chang…

39.手机导航

手机导航 html部分 <div class"phone"><div class"content"><img class"active" src"./static/20180529205331_yhGyf.jpeg" alt"" srcset""><img src"./static/20190214214253_hsjqw…

DHCP部署与安全详解

文章目录 一、DHCP是什么&#xff1f;二、DHCP相关概念三、DHCP优点四、DHCP原理1. 客户机发送DHCP Discovery广播包&#xff08;发现谁是DHCP服务器&#xff09;2. 服务器响应DHCP Offer广播包3. 客户机发送DHCP Request广播包4. 服务器发送DHCP ACK广播包 五、DHCP续约六、部…

ElasticSearch基本使用--ElasticSearch文章一

文章目录 官网学习必要性elasticsearch/kibana安装版本数据结构说明7.x版本说明ElasticSearch kibana工具测试后续我们会一起分析 官网 https://www.elastic.co/cn/ 学习必要性 1、在当前软件行业中&#xff0c;搜索是一个软件系统或平台的基本功能&#xff0c; 学习Elastic…

使用 OpenCV 进行图像模糊度检测(拉普拉斯方差方法)

写在前面 工作中遇到&#xff0c;简单整理人脸识别中&#xff0c;对于模糊程度较高的图像数据&#xff0c;识别率低&#xff0c;错误率高。虽然使用 AdaFace 模型&#xff0c;对低质量人脸表现尤为突出。但是还是需要对 模糊程度高的图像进行丢弃处理当前通过阈值分类&#xff…

Java开发中的分层开发和整洁架构

分层开发(横向拆分) 分层开发的概念: maven多模块开发项目管理.可以利用这种管理功能,实现一个项目的多层次模块开发–分层开发. 比如,当前项目HelloController依赖HelloService 这样做目的: 复杂开发过程.解耦(不调整依赖关系,无法解耦).分层开发(横向拆分)和纵向拆分的区别…

c# 此程序集中已使用了资源标识符

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 CS1508 此程序集中已使用了资源标识符“BMap.NET.WindowsForm.BMapControl.resources” BMap.NET.WindowsForm D:\MySource\Decompile\BMap.NET.WindowsForm\CSC 1 活动 运行程序时&a…

【机器学习】Feature Engineering and Polynomial Regression

Feature Engineering and Polynomial Regression 1. 多项式特征2. 选择特征3. 缩放特征4. 复杂函数附录 首先&#xff0c;导入所需的库&#xff1a; import numpy as np import matplotlib.pyplot as plt from lab_utils_multi import zscore_normalize_features, run_gradien…

session反序列化+SoapClientSSRF+CRLF

文章目录 session反序列化SoapClientSSRFCRLF前言bestphps revengecall_user_func()方法的特性SSRFCRLF组合拳session反序列化 解题步骤总结 session反序列化SoapClientSSRFCRLF 前言 从一道题分析通过session反序列化出发SoapClientSSRF利用CRLF解题 bestphp’s revenge 首…

超详细的74HC595应用指南(以stm32控制点阵屏为例子)

74HC595是一款常用的串行输入/并行输出&#xff08;Serial-in/Parallel-out&#xff09;移位寄存器芯片&#xff0c;在数字电子领域有着广泛的应用。它具有简单的接口和高效的扩展能力&#xff0c;成为了许多电子爱好者和工程师们的首选之一。本文将深入介绍74HC595芯片的功能、…

UE5、CesiumForUnreal加载无高度地形

文章目录 1.实现目标2.实现过程3.参考资料1.实现目标 在UE5中,CesiumForUnreal插件默认的地形都是带高度的,这里加载没有高度的地形,即大地高程为0,GIF动图如下: 2.实现过程 参考官方的教程,下载无高度的DEM,再切片加载到UE中。 (1)下载无高度地形DEM0。 在官方帖子…

网络安全(黑客)自学——从0开始

为什么学习黑客知识&#xff1f;有的人是为了耍酷&#xff0c;有的人是为了攻击&#xff0c;更多的人是为了防御。我觉得所有人都应该了解一些安全知识&#xff0c;了解基本的进攻原理。这样才可以更好的保护自己。这也是这系列文章的初衷。让大家了解基本的进攻与防御。 一、怎…

学习中遇到的好博客

c日志工具之——log4cpp ECU唤醒的本质就是给ECU供电。 小文件&#xff1a;零拷贝技术 传输大文件&#xff1a;异步 IO 、直接 IO&#xff1a;如何高效实现文件传输&#xff1a;小文件采用零拷贝、大文件采用异步io直接io (123条消息) Linux网络编程 | 彻底搞懂…

Pytest学习教程_装饰器(二)

前言 pytest装饰器是在使用 pytest 测试框架时用于扩展测试功能的特殊注解或修饰符。使用装饰器可以为测试函数提供额外的功能或行为。   以下是 pytest 装饰器的一些常见用法和用途&#xff1a; 装饰器作用pytest.fixture用于定义测试用例的前置条件和后置操作。可以创建可重…

6.2.tensorRT高级(1)-第一个完整的分类器程序

目录 前言1. CNN分类器2. 补充知识2.1 知识点2.2 智能指针封装 总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT …

【雕爷学编程】MicroPython动手做(13)——掌控板之RGB三色灯

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

第26天-秒杀服务(秒杀系统设计与实现)

1.秒杀设计 1.1.秒杀业务 秒杀具有瞬间高并发特点&#xff0c;针对这一特点&#xff0c;必须要做限流异步缓存&#xff08;页面静态化&#xff09;独立部署。 限流方式&#xff1a; 前端限流&#xff0c;一些高并发的网站直接在前端页面开始限流&#xff0c;例如&#xff1a…
最新文章