免费自助建设网站,unsplash素材网站,中国招投标采购网官网,无货源电商怎么做一般会如何实现文件传输#xff1f;
服务器提供文件传输功能#xff0c;需要将磁盘上的文件读取出来#xff0c;通过网络协议发送到客户端。如果需要你自己编码实现这个文件传输功能#xff0c;你会怎么实现呢#xff1f;
通常#xff0c;你会选择最直接的方法#xf…一般会如何实现文件传输
服务器提供文件传输功能需要将磁盘上的文件读取出来通过网络协议发送到客户端。如果需要你自己编码实现这个文件传输功能你会怎么实现呢
通常你会选择最直接的方法从网络请求中找出文件在磁盘中的路径后如果这个文件比较大假设有 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-DMAThe Scatter-Gather Direct Memory Access技术还可以再去除 Socket 缓冲区的拷贝这样一共只有 2 次内存拷贝。 实际上这就是零拷贝技术。 相关视频推荐
手写用户态协议栈以及零拷贝的实现
服务器性能优化异步处理有哪些不一样的
用户态网络缓冲区设计-ringbuffer、chainbuffer
免费学习地址c/c linux服务器开发/后台架构师
需要C/C Linux服务器架构师学习资料加qun812855908获取资料包括C/CLinuxgolang技术NginxZeroMQMySQLRedisfastdfsMongoDBZK流媒体CDNP2PK8SDockerTCP/IP协程DPDKffmpeg等免费分享 它是操作系统提供的新函数同时接收文件描述符和 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 指令。