优化 PHP 大文件下载速度至万兆,让 Nextcloud 支持万兆网络 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
raysonx
V2EX    PHP

优化 PHP 大文件下载速度至万兆,让 Nextcloud 支持万兆网络

  raysonx 2019-04-15 01:11:02 +08:00 21842 次点击
这是一个创建于 2373 天前的主题,其中的信息可能已经有所发展或是发生改变。

背景

最近在 HP Microserver Gen8 上重新搭建了 Nextcloud (在虚拟机里面容器里,基于 PHP 7.2 ),可惜通过 virtio 虚拟万兆网络进行下载,SSD 上文件的下载速度不超过 260MiB/s,机械硬盘上文件的下载速度不超过 80MB/s。要知道直接本地访问时,SSD 能达到 550MB/s 左右,机械硬盘平均 130MB/,不甘心(这时 PHP 进程的 CPU 占用率很低,说明根本没有达到 CPU 执行瓶颈)。

排除了网络问题后,我在存储目录上搭建了一个 Nginx 进行测试,发现通过 Ngninx 直接下载文件几乎能达到本地直接访问的性能。于是,下载速度慢的锅就落在了 PHP 的性能上。

调研

经过一番调研,Nextcloud 的 WebDav 服务是基于 sabre/dav 的框架开发的。于是找到了 sabre/dav 的源码,最后定位到下载文件代码的位置:3rdparty/sabre/http/lib/Sapi.php 。原来 sabre/dav 是通过调用 stream_copy_to_stream 将要下载的文件拷贝到 HTTP 输出的:直接把文件流和 PHP 的输出流进行对拷,之前并没有其他的读写操作,说明瓶颈就在这一行代码。

// 3rdparty/sabre/http/lib/Sapi.php // ... if (is_resource($body) && 'stream' == get_resource_type($body)) { if (PHP_INT_SIZE !== 4) { // use the dedicated function on 64 Bit systems stream_copy_to_stream($body, $output, (int) $contentLength); } else { // ... 

我本人并不是 PHP 程序员,于是开始了漫长的搜索。Google 娘告诉我 PHP 专门提供了fpassthru函数提供高性能文件下载,于是我修改代码把 stream_copy_to_stream 换成了fpassthru

// 3rdparty/sabre/http/lib/Sapi.php // ... if (is_resource($body) && 'stream' == get_resource_type($body)) { if (PHP_INT_SIZE !== 4) { // use the dedicated function on 64 Bit systems // stream_copy_to_stream($body, $output, (int) $contentLength); fpassthru($body); // 改动这一行 } else { // ... 

测试了一下,发现下载速度直接打了鸡血,440-470 MiB/s。可惜 fpassthru 只能把文件输出到结尾,不能只输出文件的一部分(为了支持断点续传和分片下载)。另外翻了一下 sabre/dav 的 issues,发现 sabre/dav 不用fpassthru的另外一个原因是有些版本的 PHP 中fpassthru函数存在 BUG。

继续深入

那为什么stream_copy_to_stream速度和fpassthru差距大得不科学呢?只能去读 PHP 的源码了,幸好 C 语言是我的强项。 我发现,fpassthru函数和stream_copy_to_stream函数实现是及其类似的:先尝试把源文件创建为内存映射文件(通过调用 mmap ),如果成功则直接从内存映射文件拷贝到目的流,否则就读到内存中进行传统的手动拷贝。差别来了,stream_copy_to_stream的第三个参数是要拷贝的字节数,可惜如果这个值大于 4MiB,PHP 就拒绝创建内存映射文件,直接回退到传统拷贝。

解决方法

在循环中调用stream_copy_to_stream,每次最多拷 4MiB:

// 3rdparty/sabre/http/lib/Sapi.php // ... if (is_resource($body) && 'stream' == get_resource_type($body)) { if (PHP_INT_SIZE !== 4) { // use the dedicated function on 64 Bit systems // 下面是改动的部分: // allow PHP to use mmap by copying in 4MiB chunks $chunk_size = 4 * 1024 * 1024; stream_set_chunk_size($output, $chunk_size); $left = $contentLength; while ($left > 0) { $left -= stream_copy_to_stream($body, $output, min($left, $chunk_size)); } } else { // ... 

测试了一下,结果令人震惊:下载速度几乎和本地读取无异了:SSD 文件的下载速度超过了 500 MB/s,甚至超过了 fpassthru 的速度(大概是因为缓冲区开的比fpassthru大)。

我又试着创建了一个 10G 大小的 sparse 文件 ( truncate -s 10G 10G.bin ),Linux 在读取 sparse 文件时可以立即完成,可以用来模拟如果硬盘速度足够快的情况。继续测试,发现下载速度超过了 700MiB/s,已经接近万兆网络的传输极限。这时 PHP 进程的 CPU 占用率已经达到 100%,说明瓶颈在 CPU 性能上了。

总结

stream_copy_to_stream 拷贝流时,如果 source 是文件并且每次拷贝小于 4MiB,PHP 会用内存映射文件对拷贝进行加速。超过 4MiB 后就会回退到传统读取机制。

后续

向 Sabre 项目提了 PR:https://github.com/sabre-io/http/pull/119。如果各位也在玩 Nextcloud 并且遇到了下载速度瓶颈,可以试着打一下我这个补丁。

第 1 条附言    2019-04-15 15:04:09 +08:00
感谢各位的支持和建议,先统一回复一些内容:

1. 部分小伙伴提到了 Nginx 的 X-accel:如果 web 服务器用的是 Nginx 的话,可以通过设置一个 HTTP header 将文件输出转交给 Nginx,可获得更快的下载速度。这确实是个可行的优化思路,毕竟 Nginx 性能优越,而且输出文件的实现基于 sendfile 系统调用,理论上效率更高。目前不知道 Nextcloud 或者 Sabre 有没有计划针对 Nginx 做优化,后续有时间可以试着跟进一下。

2. stream
第 2 条附言    2019-04-15 15:08:22 +08:00
2. 目前看来 PHP 的 stream_copy_to_stream 的性能不佳,可优化的地方非常多,可以考虑对这个函数进行优化,贡献给上游社区。

3. 有小伙伴担心对 sabre 的“魔改”会对其他地方造成影响。我觉得这应该不是魔改吧。。。。
第 3 条附言    2019-04-15 15:12:11 +08:00
忘了在正文中补充机械硬盘下载速度的提升:
机械硬盘的下载速度从优化前的 70-80MB/s 提升到了 100 - 120 MB/s。我没有仔细去研究机械硬盘下载速度提升的原因,可能是我调大了拷贝的缓存区大小所致。
第 4 条附言    2019-04-15 21:17:35 +08:00
补充:PHP 核心源码中拒绝映射 4MiB 以上文件的代码在这里: https://github.com/php/php-src/blob/623911f993f39ebbe75abe2771fc89faf6b15b9b/main/streams/mmap.c#L34
95 条回复    2021-06-30 10:49:38 +08:00
zk8802
    1
zk8802  
   2019-04-15 01:14:15 +08:00 via iPhone   2
赞楼主刨根问底的精神!
Q4h7388nR28s95fa
    2
Q4h7388nR28s95fa  
   2019-04-15 01:19:37 +08:00
厉害!楼主专研精神真棒!
zhs227
    3
zhs227  
   2019-04-15 01:19:46 +08:00   1
没玩过这么高级的装备,不过非常佩服楼主,顶一下友情支持
另外不清楚有没有人知道,nginx 的那个 sendfile 和这个 mmap 的拷贝机制是不是一回事
raysonx
    4
raysonx  
OP
   2019-04-15 01:25:55 +08:00   1
@zhs227 是的`sendfile`的性能更高,直接让内核对拷两个文件描述符,连内核态 /用户态拷贝都不用。但是 PHP 至今没有利用`sendfile`,包括`fpassthru`。
yufz
    5
yufz  
   2019-04-15 01:38:44 +08:00 via Android
佩服佩服,真 极客
GG668v26Fd55CP5W
    6
GG668v26Fd55CP5W  
   2019-04-15 02:24:47 +08:00 via iPhone
厉害啊
sxcccc
    7
sxcccc  
   2019-04-15 03:11:52 +08:00 via iPhone
aws 的 ec2 高端配置 东京节点 首页 500kb 打开速度一流 内容分发都是 4gb 大包依然能迅速下载 参考 www.dxqq.net
lzxgh621
    8
lzxgh621  
   2019-04-15 03:28:13 +08:00 via iPhone
我这边 程序本体都跑不利索
slowgen
    9
slowgen  
   2019-04-15 03:29:44 +08:00
很棒,最近在团队内部推 nextcloud,以及基于 Collabora 的办公文档协作,先收藏留作备用了.
tony601818
    10
tony601818  
   2019-04-15 04:14:54 +08:00 via Android
厉害,难得有真正有意义的话题了!
lihongming
    11
lihongming  
   2019-04-15 05:05:27 +08:00 via iPhone
赞,可以考虑测一下 2M 一个循环,看是不是会更早达到 CPU 瓶颈,那样的话就该考虑自己修改 stream_copy_to_stream 源码放宽限制,以获得更高性能了。
mwftts
    12
mwftts  
   2019-04-15 07:11:47 +08:00 via Android
好久没在 app 第一页看到这样的技术贴,今天一天心情肯定会不错
lazyyz
    13
lazyyz  
   2019-04-15 07:26:35 +08:00 via Android
厉害,佩服楼主这折腾劲!
taresky
    14
taresky  
   2019-04-15 07:44:48 +08:00 via iPhone
厉害!
akagishigeru
    15
akagishigeru  
   2019-04-15 08:06:14 +08:00 via iPhone
一大早就学习了
carlclone
    16
carlclone  
   2019-04-15 08:11:37 +08:00 via Android
强,基础好扎实
mokeyjay
    17
mokeyjay  
   2019-04-15 08:16:40 +08:00
强无敌,点赞
CallMeReznov
    18
CallMeReznov  
   2019-04-15 08:17:22 +08:00 via Android
这才是真正的干货啊
zvcs
    19
zvcs  
   2019-04-15 08:24:02 +08:00 via Android
谢谢楼主的分享
Canon1014
    20
Canon1014  
   2019-04-15 08:24:28 +08:00
目瞪口呆
zuokanyunqishi
    21
zuokanyunqishi  
   2019-04-15 08:28:04 +08:00 via Android
点赞
fengtalk
    22
fengtalk  
   2019-04-15 08:34:14 +08:00
收藏了,佩服和赞赏楼主的这种探索精神。
Edwards
    23
Edwards  
   2019-04-15 08:34:58 +08:00
收藏
zzxCNCZ
    24
zzxCNCZ  
   2019-04-15 08:40:26 +08:00
赞楼主,厉害了
R18
    25
R18  
   2019-04-15 08:44:21 +08:00
厉害了!打破砂锅闻到底
fox0001
    26
fox0001  
   2019-04-15 08:47:39 +08:00 via Android
点赞! nextcloud 15 之前,性能低下,我只是从树莓派搬到 x8350。一直以为是 PHP 背的锅,没想到楼主还能找出具体原因
SupperMary
    27
SupperMary  
   2019-04-15 08:57:52 +08:00 via Android
很强
eluotao
    28
eluotao  
   2019-04-15 09:14:45 +08:00
技术贴 要收藏...回头看看 NAS 有没有优化的空间.
whatsmyip
    29
whatsmyip  
   2019-04-15 09:18:47 +08:00
很强
yngby
    30
yngby  
   2019-04-15 09:21:08 +08:00
牛逼牛逼
polymerdg
    31
polymerdg  
   2019-04-15 09:35:00 +08:00
牛逼
hst001
    32
hst001  
   2019-04-15 09:40:35 +08:00 via Android
666
SbloodyS
    33
SbloodyS  
   2019-04-15 09:42:35 +08:00
牛逼
sorshion
    34
sorshion  
   2019-04-15 09:46:20 +08:00
基础很扎实,厉害
liuxu
    35
liuxu  
   2019-04-15 09:46:40 +08:00
这波操作可以的
syahd
    36
syahd  
   2019-04-15 09:49:18 +08:00 via Android
⊙⊙!这就是开源的魅力啊,有需要就可以自己改。不过确定魔改这一部分的代码不会对其他地方造成影响吗?
dapang1221
    37
dapang1221  
   2019-04-15 09:59:02 +08:00
厉害了
bzi
    38
bzi  
   2019-04-15 10:07:08 +08:00
厉害啊
tailf
    39
tailf  
   2019-04-15 10:14:55 +08:00
服了
reeble
    40
reeble  
   2019-04-15 11:00:37 +08:00
大佬大佬
sheeta
    41
sheeta  
   2019-04-15 11:08:16 +08:00
佩服佩服
zhujinliang
    42
zhujinliang  
   2019-04-15 11:09:12 +08:00 via iPhone
使用 nginx 的 X-Accel-Redirect 可不可行呢
ipengxh
    43
ipengxh  
   2019-04-15 11:27:24 +08:00
厉害了
liuxyon
    44
liuxyon  
   2019-04-15 11:30:40 +08:00
厉害
yytsjq
    45
yytsjq  
   2019-04-15 11:34:39 +08:00
@zhujinliang X-Accel-Redirect 相比 fpassthru 应该更好些吧
dalieba
    46
dalieba  
   2019-04-15 11:42:08 +08:00 via Android
希望 Sabre 项目早日接纳楼主的改进,新版本早日发布。
klusfq
    47
klusfq  
   2019-04-15 11:43:38 +08:00 via iPhone
膜拜楼主大佬
zzxx3322
    48
zzxx3322  
   2019-04-15 11:48:53 +08:00
楼主有遇到上传瓶颈吗?官方默认最多同时上传三个任务,关键速度跑不满,我没有详细测试是不是网络或者硬件问题导致速度跑不满,但是我感觉你的问题和这个问题也应该是相同的锅,提一下,可以给点意见嘛?
duola
    49
duola  
   2019-04-15 12:12:25 +08:00
折腾精神,厉害!
raysonx
    50
raysonx  
OP
   2019-04-15 12:23:28 +08:00 via Android
@zzxx3322 上传速度确实比下载慢很多。Nextcloud 的上传机制比较复杂,等有时间研究一下开个帖分享。
moonfly
    51
moonfly  
   2019-04-15 12:27:02 +08:00
技术贴必须要支持,
虽然自己的功力远远没有达到 LZ 的级别,
但能看到这样的帖子,真的是一种享受!
Huelse
    52
Huelse  
   2019-04-15 12:32:18 +08:00
真是一篇干货,感谢感谢!!
Actrace
    53
Actrace  
   2019-04-15 12:32:46 +08:00
还有一个方案,文件输出完全交给 Nginx 去做,PHP 只负责处理输出前逻辑。
这里需要用到 Nginx 的一个特性 X-Accel-Redirect,不过这样整套程序就和 Nginx 绑定到一起了。
zjq123
    54
zjq123  
   2019-04-15 13:06:32 +08:00 via Android
你们下载速度达到几百兆每秒?
dnsaq
    55
dnsaq  
   2019-04-15 13:07:57 +08:00 via iPhone
目瞪口呆 我都看懵了。
tongz
    56
tongz  
   2019-04-15 13:17:06 +08:00
奈何本人没文化, 一句卧槽走天下
laozhoubuluo
    57
laozhoubuluo  
   2019-04-15 13:27:00 +08:00
啥也不说了,点赞!!
ultimate010
    58
ultimate010  
   2019-04-15 13:36:56 +08:00
真心点赞,我自己搭建的局域网 samba 和 nfs 等文件服务,速度也没法跑满千兆网卡,查了下参数优化了下 samba aio,有点提升,但是仍然无法满速,没思路就凑合用了。
KasuganoSoras
    59
KasuganoSoras  
   2019-04-15 13:42:28 +08:00


确实是快了很多,在千兆服务器上测试的
KasuganoSoras
    60
KasuganoSoras  
   2019-04-15 13:44:07 +08:00   1
@ultimate010 #58 局域网 samba 我测试千兆是可以跑满的,传文件速度稳定在 110MB/s 左右,如果跑不满可能是 samba 版本比较低或者其他问题
killerv
    61
killerv  
   2019-04-15 13:51:30 +08:00
厉害了
cfcboy
    62
cfcboy  
   2019-04-15 14:22:55 +08:00
感谢楼主的分享,做个记号。
BCy66drFCvk1Ou87
    63
BCy66drFCvk1Ou87  
   2019-04-15 14:46:06 +08:00
awesome
fengci
    64
fengci  
   2019-04-15 15:11:36 +08:00
mk
panlilu
    65
panlilu  
   2019-04-15 16:25:56 +08:00
硬核 debug
ultimate010
    66
ultimate010  
   2019-04-15 18:42:50 +08:00
@KasuganoSoras 谢谢,我用 docker 跑的,最新的 dperson/samba,小机器 cpu 是 Intel(R) Atom(TM) CPU D525 @ 1.80GHz,机械硬盘,全速的时候也就 50mb 左右,以前调出过写入 80mb,读取也就 30-40mb,cpu 好像没有跑满,感觉自己的配置有点问题。
KasuganoSoras
    67
KasuganoSoras  
   2019-04-15 18:58:38 +08:00   1
@ultimate010 #66 这应该就是 CPU 性能瓶颈问题了,我手上也有一台 Atom D2550 的工控主机,装了 Samba 测试也是跑不满千兆,速度在 100-400Mbps 左右浮动,就上不去了
kookxiang
    68
kookxiang  
   2019-04-15 19:15:15 +08:00
应该用 sendfile 吧
ben1024
    69
ben1024  
   2019-04-15 19:35:05 +08:00
厉害
intsilence
    70
intsilence  
   2019-04-15 21:06:07 +08:00
手动点赞!
raysonx
    71
raysonx  
OP
   2019-04-15 21:14:45 +08:00
@KasuganoSoras 截图中是打了补丁后的速度吗?之前是多少? CPU load 有没有跑满?
KasuganoSoras
    72
KasuganoSoras  
   2019-04-15 21:19:29 +08:00
@raysonx #71 之前大概是 20 ~ 40M/s 左右,CPU 的话基本上不可能跑满的……至少是宽带先跑满
因为 CPU 是 32 核 64 线程,但是下载的时候看到 CPU 占用率明显比之前高了,速度也快了很多
dandycheung
    73
dandycheung  
   2019-04-15 21:21:27 +08:00 via Android
@zhs227 不是一回事。
KasuganoSoras
    74
KasuganoSoras  
   2019-04-15 21:21:51 +08:00
@raysonx #71 这个速度其实不是固定的,一直在跳来跳去,可能和我本地网络有关,我看到有几秒钟速度上到了 97MB/s,然后又掉到 60 左右,不过已经算很不错了
raysonx
    75
raysonx  
OP
   2019-04-15 21:58:54 +08:00
@KasuganoSoras 有时间的话可以试试在服务器上本地测速,排除网络影响。方法是直接用 curl 命令下载文件:

curl -o /dev/null --user 'username:password' -H hostname http://127.0.0.1/remote.php/webdav/文件名
BooksE
    76
BooksE  
   2019-04-15 23:18:54 +08:00
你们都是点赞?只有我是羡慕 lz 有这个能力
wmwwmv
    77
wmwwmv  
   2019-04-16 01:41:15 +08:00 via Android
这对我很有用,感谢楼主
ericgui
    78
ericgui  
   2019-04-16 06:06:18 +08:00
@BooksE 看来学 C 还是挺有用的
tankren
    79
tankren  
   2019-04-16 07:45:09 +08:00 via Android
收藏了 谢谢
silencefent
    80
silencefent  
   2019-04-16 09:01:50 +08:00
速度提高 3 倍,cpu 跑满载还是有点划不来吧,gen8 好歹也是 3.5G 起步的 4C8T 服务器
ganbuliao
    81
ganbuliao  
   2019-04-16 09:16:56 +08:00
牛逼 我觉得 还是比较适合拧小螺丝钉
ganbuliao
    82
ganbuliao  
   2019-04-16 09:17:49 +08:00
还是觉得自己比较适合拧小螺丝钉 (滑稽
wttx
    83
wttx  
   2019-04-16 09:31:16 +08:00 via Android
Mark 一下,以后有用,,
telami
    84
telami  
   2019-04-16 10:22:34 +08:00
开源的魅力,心向往之
lzj307077687
    85
lzj307077687  
   2019-04-16 14:06:03 +08:00
敬佩!
nyaruko
    86
nyaruko  
   2019-04-16 15:01:52 +08:00
万兆。。厉害了。。家里千兆网完全不担心这些。
knightgao2
    87
knightgao2  
   2019-04-16 16:03:39 +08:00
厉害厉害了
Jaeger
    88
Jaeger  
   2019-04-16 16:54:58 +08:00
反手就是一赞
abccccabc
    89
abccccabc  
   2019-04-19 11:13:09 +08:00
这个贡献可大了。
iwishing
    90
iwishing  
   2019-05-07 16:13:36 +08:00
这个就是工匠精神,hacker 精神,打破沙锅问到底的精神
请问,您秃了没有?
Chenamy2017
    91
Chenamy2017  
   2019-05-07 16:19:07 +08:00
牛!
JRay
    92
JRay  
   2019-05-07 20:53:29 +08:00
大佬大佬
zlfoxy
    93
zlfoxy  
   2019-05-07 23:38:33 +08:00
厉害,关键是这么复杂的东西,楼主能解释的这么清楚。膜拜。
pupboss
    94
pupboss  
   2020-08-20 23:09:03 +08:00
楼主好,我想请教个问题,NextCloud 使用网页版或者 WebDAV 处理大文件的时候,取消下载文件之后,php 还是会不断的读硬盘,并且占满 IO,如果点了一个视频关闭再开再关闭,磁盘会卡的不能用,同时我也用 Nginx 测试过静态文件,只有在需要读盘的时候才会有 20M/s 左右的 IO,然后立刻就不读了,这个问题有解吗,我看官方已经合并了你最新的代码,应该不是这个导致的
aod321
    95
aod321  
   2021-06-30 10:49:38 +08:00
大佬牛!
关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1068 人在线   最高记录 6679       Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 33ms UTC 17:59 PVG 01:59 LAX 10:59 JFK 13:59
Do have faith in what you're doing.
ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86