https://gist.github.com/lsk569937453/e0496754cf3ebe740a33a7759516015e
我自己写了个 demo ,假设开 50 个线程下载,每个线程下载时间为 10s 钟,可以看到最后写入文件的时间才 1s 不到。
所以下载文件时,瓶颈是网络 IO 吧,操作文件 IO 的时间可以忽略不计了。所以是不是下载到一个文件中更优?
1
Karte 221 天前 1
1. 文件占用
2. 内容覆盖 |
2
Te11UA 221 天前
> 所以下载文件时,瓶颈是网络 IO 吧,操作文件 IO 的时间可以忽略不计了。所以是不是下载到一个文件中更优?
这一句话不是前后冲突吗 |
3
kenvix 221 天前 25
没有为什么,就是写代码的太菜,连 pre-allocate+seek 都不会
|
4
ppllss 221 天前
感觉是利用网速吧。很多时候 1 个文件下载就 100KB/s 那么要下载好久。分多个文件,每个文件 100KB/s 不是更快了?
为什么 1 个文件下载就 100KB/s 不清楚呀。带宽几百 M 下载一个文件我也没有跑满 |
5
kenvix 221 天前 1
实际上除了 IDM 主流下载器都是不需要合并的
|
6
huixia0010 221 天前
动动脑子啊,万一其中一片数据出问题要重下的时候,你一个文件里怎么搞?
|
7
kenvix 221 天前 8
@huixia0010 #6 动动脑子啊,你说的这种问题分多个文件就能解决?
|
8
jifengg 221 天前
首先,迅雷、FDM ,都是下载到同一个大文件里的。
这种方式,首先需要申请这个文件的空间,也就是你刚开始下载 10g 的文件,立刻就要在硬盘里创建 10g 的文件。 然后,写数据是一个持续的过程,多线程需要自己调度好文件占用的问题。你 demo 是等 10 秒后一次性写入。 分段需要处理临时文件,各有优劣,看自己更熟悉哪种方式了。 |
9
AceDogs 221 天前
文件分开可以解决更多普遍的问题,而且文件名是天生的索引信息,支持的功能更广泛。
单一文件在在特定情况下确实很好,比如知道文件总大小 等等。个人感觉好像单一文件的优势不是特别多。 结合一下也是一种方案, 用较小的缓冲区不断下载, 同时完成的数据合并入大文件中。实现方案各有优缺点。 |
10
yazinnnn0 221 天前
感觉下载到一个大文件没啥优势呢
|
11
tool2dx 221 天前
|
12
kenvix 221 天前 2
@jifengg #8 我以前做过多线程下载器,关于文件分配,直接向 OS 申请预分配一块存储空间就行了,预写入 10G 那是 HDD 时代老 Windows 的东西。关于线程调度,直接开个 BlockingQueue<<Index, ByteBuffer>>然后由专门的 Writer 负责写就行了,其他下载线程只需要负责把下到的 block(比如设定每个 block 为 512K)挂到 queue 上就完事
IDM 那种分文件下载再合并,对于大文件下载来说是不可接受的开销。除了菜逼我不知道还有什么理由会这样做 |
13
lsk569937453 OP @yazinnnn0 省去了合并文件的时间以及删除临时文件的时间。代码上更简洁了。。。
|
14
lsk569937453 OP @huixia0010 有一个分片出问题,那么重新下载这个分片,然后文件 seek 到指定的位置,重写那个分片就可以了。
|
15
Karte 221 天前 16
多线程多文件时是由下载线程写入到文件中. 如果是多线程单文件则是由单独的线程负责写入.
多线程多文件设计简单, 下载完直接写入. 最后由最后一个线程负责文件合并. 多线程单文件则是高阶写法, 需要控制写入的位置之类的. 别一口一个菜逼, 都是从菜鸟过来的. 只有适合的方案, 没有完美的设计. |
16
mayli 221 天前 1
一般都是写到一个大文件吧,下载到单独文件,相当于 IO 两次,除了某些场景下,现在的 OS 对于这个没有特殊差别。
但是对于真的大文件,比如 10GB 以上的,你这种写两遍的操作会占用两倍的磁盘空间,而且对于 HDD ,一边读一边写会巨慢。 所以,这么做,单纯的是菜(不会 seek )。 |
17
IvanLi127 221 天前
只要有写文件的缓冲区,往一个大文件里写正常是更优解,毕竟事后合并文件的话磁盘 IO 还得吃一波。
话说回来到底啥软件会存碎片文件最后再合并 |
18
lltlo 221 天前
同一机器多线程下载没啥可玩的,有没有成熟的多机器同时下载同一个文件的工具?公司网络做了限制,每个终端有带宽有限,所以想用多台 pc 来下同一个文件的不同部分,最后合并起来,类似于分布式下载工具。
github 上找了款 distributed-downloader ,有些小问题,自己改吧改吧,算是可以用,但是不太方便。 |
19
augustheart 221 天前
纯看写代码的怎么想。通常情况下,下到同一个文件中都是最优解。
但是,在不知道最终文件多大但是又可以多线下载的时候(流媒体可能碰到),分块下载最后合并应该是一个相对稳妥的办法。 |
20
MrKrabs 221 天前
去问写程序的
|
21
guanzhangzhang 221 天前 6
|
22
huixia0010 221 天前 1
如果下载的是不清楚长度的流媒体直播流呢?
|
23
nolog 221 天前
@guanzhangzhang #21 不懂就问,用 nohup 有什么不好的地方吗?
|
24
Maerd 221 天前 1
因为实现起来通常更简单,也更容易处理错误和恢复下载
|
25
Ashe007 221 天前 via iPhone
说一个都没有关注的关键点:内存溢出,如果服务器剩余可用内存为 4 个 G ,文件为 5G 。拆分就是避免内存溢出的场景,因此大文件才需要分段上传/下载。
如果文件是可预料的小,当然没必要拆分 |
26
FranzKafka95 221 天前
@nolog nohub 当然没有 systemd 好,systemd 功能可高级多了
|
27
Ashe007 221 天前 via iPhone
此外,还减轻了网络压力和内存压力,降低了系统负载
|
28
monkeyWie 221 天前 2
这个我作为 gopeed 下载的作者可以答一下,上面有 v 友其实也已经说的很对了,分多个临时文件下载再合并就是一种垃圾实现,没错就是说的 IDM ,预分配 + seek 写才是正确的高性能的对硬盘损耗最小的。
|
29
aababc 221 天前
@guanzhangzhang 又学了点新知识,mktemp 这个还从来没用过
|
30
cheng6563 221 天前
Windows 需要管理员权限才能预分配大文件空间吧。
|
31
0312birdzhang 221 天前
完全可以,先根据 content-length 创建好文件大小,再分片写入(重写)就行了。同时写入一个文件和同时写入多个文件,对磁盘来说是一样的吧。
|
32
guanzhangzhang 221 天前
@nolog nohub 启动,进程意外退出就 g 了,systemd 有重试,cgroup ,前后顺序依赖,io 限制,条件启动,
|
33
guanzhangzhang 221 天前
@aababc 如果写单元测试,好多就是语言里库的 mktmp 啥的方法创建临时文件或者目录
|
34
shuax 221 天前 3
也就只有 IDM 才这样吧。我再来个暴论,多线程也是不需要的,完全可以单线程实现多并发。
|
35
lesismal 221 天前
@kenvix
如果写到多个文件, 每个文件可以自带上一个元信息, 即使任务中途失败导致下载器退出之类的, 以后还可以从上次下载的地方继续. 直接写入到目标文件, 没有这个元信息, 如果中途失败退出了, 下次只能从头来. 对于大文件低带宽不稳定, 不能确保单次操作下载完的情况下, 直接写目标文件不划算; 而即使是 10G 这种大文件, 本地多个分段文件做一次合并的开销和耗时也几乎可以忽略不计. 另外, 分段下载如果对方响应的数据不是 trunked, 而是 content-length, 不同平台或许还可以利用 zero copy 优化避免先读取到内存再写入文件, 既减少应用层内存开销也提高速度, 多线程直接写入 queue 再写入目标文件的方式不好做这些. 不过这些本地 io 和只是读写 buffer 的内存开销本来也不大, 即使能做提升也不会太大. |
36
lisxour 221 天前
省流版
小文件分片:逻辑简单易实现,会多一点时间、性能消耗( 2024 年了,完全可以忽略) 单文件:逻辑相对复杂点,要求对多线程 io 操作有一定了解,作为用户而言(我自己),没发现什么很明显的体验提升,迅雷、百度云就是单文件的,其实也就那样 |
37
auroraccc 221 天前
是的,多文件合并还得花时间,不如直接把多个分片写到一个文件的不同 range 里
|
38
lovezhangdada 221 天前
@monkeyWie
老师,我想请教一下 aria2 和 gopeed 实现原理一样吗》? |
39
kenvix 221 天前 1
@lesismal #35 关于续传,他没问我也就没说,实现上一般有一个 bitmap 控制文件写入情况,你说的所有问题用一个 bitmap 就能解决了。另外如果不想用 bitmap ,创建的时候指定文件类型为稀疏,然后直接询问系统哪些块是稀疏的,直接从稀疏块开始,也是可以的。
|
41
monkeyWie 221 天前
@lovezhangdada 你指的是哪个实现原理?如果是指 http 下载的话基本是差不多,也就是我说的预分配 + seek 写,当然 aria2 是用的多线程 gopeed 用的多协程所以更省资源
|
42
augustheart 221 天前
@shuax 很难说。大部分情况下,多线程不会消失,只会转移。从调用者来看是单线程没啥问题
|
43
monkeyWie 221 天前
@shuax 这还真不是暴论,现代化的网络编程都是支持单线程高性能网络开发的(epoll 、eventloop 、协程)的,只能说现在载器还在用多线程技术的都是老古董
|
44
AsAsSaSa 221 天前
@huixia0010 直接持续追加就行,直播流只需要 1x 速度一般没多 TCP 链接的需求,而且你获取的基本一直是最末尾的数据,简单追加即可,真想多链接,每个 m4s 的请求带 content-length 就可以确定下一个分片该保存的开始位置。
|
45
monkeyWie 221 天前
@cheng6563 对的,但是不全对,windows 在没有管理员权限下也可以预分配只是比较慢,因为要做零填充,如果管理员模式就可以一瞬间分配好不管多大的容量
|
46
Anarchy 221 天前
这个谈不上菜吧,这类代码很多场景只考虑带宽能不能跑满不在意文件 io 的,能快速写完代码就是最大优点了。
|
47
augustheart 221 天前
@monkeyWie 你分析的方向错了。下载软件是为了下载速度更快,而不是省资源。多线程下载是在历史中卷速度卷出来的。而在这个过程中,select 什么的东西从一开始就存在,这是 socket 编程的基础。
|
48
TrembleBeforeMe 221 天前
是的,所以我虽然买了 IDM 也不再使用了,改为用 imfile+aria2 下载辅助插件
|
49
monkeyWie 221 天前
@augustheart #47 有没有可能既省了资源,下载速度也没落下呢,新的编程技术出来就是为了解决历史的糟粕的,开 32 个线程并发下载 cpu 干冒烟了,然而开 256 个协程并发下载 cpu 表示毫无压力,并发数还高速度还更快
|
50
augustheart 221 天前
@monkeyWie 有没有可能 io 密集型的玩意开上 100 个 cpu 也谈笑风生呢?
|
51
monkeyWie 220 天前
@augustheart #50 那你有没有听说过 io 密集的时候并发高了一样能把 cpu 打挂,当然我这里指的是多线程+IO 这种古老的东西,然后如果你还要继续反驳的话就是你说的对
|
52
augustheart 220 天前
@monkeyWie 会的,当年万兆网卡直接把 cpu 干爆,然后?算了,我先来,你是对的
|
53
Belmode 220 天前
chrome 下载不就是写到一个.downloading 临时文件里的吗。
|
54
yidinghe 220 天前
这类策略都是因为受到硬件环境限制而做出的。比如你可能觉得“下载文件时,瓶颈是网络 IO 吧”,其实对机械硬盘和 U 盘来说,瓶颈就在文件系统 IO 。
所以对于多线程下载,一个应用采取何种策略,看作者愿意考虑多少场景。不要简单地看到就说作者菜。甚至还有的人会说“现在还要什么多线程下载”,这种人也是接触的场景比较少。当然我不知道你针对的是哪个应用,还是说你自己打算写一个。 |
58
kenvix 220 天前
@Ashe007 #56 我看你似乎用 spring ,那也不应该像很多 php 用户不知道流的样子,常规的实现流式实现都只占用 buffer (例如 512KB )大小的内存
|
59
augustheart 220 天前
@yidinghe 我记得有多线程模式了(?)
|
60
augustheart 220 天前
@yidinghe 我不记得哪儿看帖有人说的。我个人很多年都是火狐了
|
61
monkeyWie 220 天前
@augustheart #60 是有的,但是要手动开起来,而且并不怎么好用
|
62
lieh222 220 天前 via Android
可能为了兼容各种文件系统,不确定目标目录挂载的 nfs ext 网盘之类文件系统可不可以这样操作文件
|
63
0o0O0o0O0o 220 天前 via iPhone
我想想怎么给它圆:可能有的 fs 不支持 seek ?
|
64
Davic1 220 天前 1
https://s3browser.com S3 broswer 从 AWS S3 上下载大文件,也是分成 N 个 100MB 的小东西下载,然后合并。
|
65
silencelixing 220 天前 1
我觉得应该是数据一致性的问题:
如果在下载过程中出现错误(例如网络中断),那么已经下载的部分可能会被破坏。如果所有数据都写入同一个文件,那么就需要重新下载整个文件。而如果数据被写入不同的文件,那么就只需要重新下载出现错误的那部分。 简要描述:单文件下载,如果出错,那么出错的代价太大了;而分文件下载,可以减少这个代价。 |
68
augustheart 220 天前
@silencelixing 单文件的设计在这个情况下一般都是带一个配置文件解决,比如迅雷和快车。
|
69
yuzii 220 天前
单文件会一次性申请较大的磁盘空间,下载暂停的时候多文件停下来不会占用太多磁盘空间
我圆得如何 |
70
LXGMAX 220 天前
比特彗星不就是分段下进同一个文件
|
71
msg7086 220 天前
|
72
w568w 220 天前 via Android 1
|
73
wangritian 220 天前
别再线程了,全是 IO 任务
|
74
nuffin 220 天前
@0o0O0o0O0o 那还不如说他用的是磁带机 哈哈哈哈哈
|
76
GrayXu 220 天前
@augustheart #68 这是正交的吧,多个小文件一样需要配置额外检错机制。主要是“如果所有数据都写入同一个文件,那么就需要重新下载整个文件” 这个论点就是错误的。
|
79
trzzzz 220 天前
用户用下载软件最终肯定是想看到 [一个] 文件,而不是多个小文件
|
80
weeei 220 天前
主流的实现就是写在一个大文件里,为了避免频繁读写会使用内存缓存,缓存满了再写入磁盘,当然就赌不会丢数据了。
|
81
trzzzz 220 天前
@Ashe007 试一下 [文件] -> [内存] -> [oss-sdk] 。你分段上传的 [源文件] 是分片好的吗,如果是已经切好片了你把 file 丢给 sdk 也没问题,它也会一点点传走
|
83
mayli 220 天前
大部分程序员,可能对网络和文件系统接口都不熟。
现代操作系统提供的网络基本上都不会出现传输错误,现代操作系统也都会提供 seek+随机写的功能。 对于 preallocate 会有各种实现方式,不过提供的结果基本一致,也就是实现了随机写。另外,即使不依赖 seek ,也可以有 mmap 这样的方式实现随机写。 再说从网络下载到磁盘,大部分操作系统也会提供 zero-copy 或者类似 pipe 的功能,即使没有,IO 操作时基本上也是使用一个固定 buffer ,比如 64k ,不会把全部内容放到内存再操作。 再说一下状态恢复,类似数据库一样,分片的下载状态一般是单独存放,比如 aria 、flashget 或者迅雷,只有分片下载完成后再去更新状态,这样即使程序崩溃,也可以下次启动从恢复点续传。 最后补充一个极端的例子,BT 下载每次传输单元是 16KB ,难不成要创建一堆 16KB 的文件,然后完成之后再合并?从文件系统的效率角度来说,从 socket 读并且单独写入一个大文件,这里可以只需要频繁调用 read(socket) + write(fd)。 但是如果是一堆小文件,你需要频繁创建和关闭文件,这两个操作在大部分操作系统,尤其是 HDD 上的文件系统,开销会非常大,你的顺序写操作会变成随机写,同时如果你的操作系统安装了杀毒软件,杀软也会在文件关闭时进行扫描,结果就是更慢了。 一般程序员在软件设计和实现的时候,都会倾向于使用资源需求最小并且吞吐量高的方案。所以当小文件没有明显优势,而且特殊场景下资源开销和性能都有明显损耗的情况下,选择大文件是一个比较自然的方案。 类似的需求还有比如数据库,就像如果网络客户端发送了一些 INSERT ,你是把他们写入小文件然后再合并呢,还是直接放到大文件中。这里的就会有和网络下载类似的权衡,当然也有不一样的地方。 |
84
cybort 220 天前 via Android
不是所有文件系统都支持你预分配空间加稀疏存储的,你得前置写入一遍磁盘,给用户的感觉就是启动延迟。
|
85
cybort 220 天前 via Android
那天找到一个远古的 bt 软件,就是下载成很多小块文件的。另外小水管时代每个小块下载都很慢,这样也方便单独做文件校验。
|
86
msg7086 220 天前 1
@w568w 你可能搞混了 preallocation 和 sparse file 。
你说的写多少用多少是 sparse file ,而快速 allocation 是立即分配你申请的空间但不初始化成零。 以前那种下载大文件要卡很久,不是因为申请空间慢,而是申请空间的时候需要清零所以慢。快速 alloc 本质上是跳过清零空间。(也意味着这种分配方式需要提权,因为跳过了清零,所以程序可以读到磁盘上原本他看不见的已删除数据。) 为了减少文件碎片,肯定是一次性分配整个文件的空间更划算。 |
87
lamquan 220 天前 1
支持楼主,因为每次下载几十 G 的大文件,IDM 合并都慢死了,对磁盘读写消耗也大。
|
88
guo4224 220 天前 via iPhone
@huixia0010 这两种方式都不可以呀
|
91
msg7086 220 天前
@llsquaer P2P 下载方式起来了以后这种就没什么用了。这些软件都是在 56k 网络的时候才有用,下一个文件要下几十分钟甚至几小时。现在网速是原来的一万倍,没意义了。
|
92
msg7086 220 天前
另外这软件还有自动拨号和下载完成自动挂断的功能,对拨号的用户实在是太友好了。
|
93
IXTUS 220 天前
bt ?
|
94
digimoon 220 天前
没想到这么多年了 idm 还是那么菜
|
95
pubby 220 天前 via iPhone
图省事吧,代码写起来简单,几个文件写入流,无脑 write 就行
性能上没啥差别,尤其现在都用固态了 |
96
jiezhi 220 天前 via iPhone
还一个除了本地文件系统是不是所有的文件系统单文件都支持多并发写入?
我知道的 hdfs 是只支持追加和复写,不支持寻址写。 |
98
wuxianliang 220 天前 via Android
IDM 俗称单线程下载之王...
|
99
Inn0Vat10n 220 天前
题外话,有些存储/driver 实现,并发读写多文件是要比并发读写单文件快的,比如英特尔的 AEP , 不过下载这个场景瓶颈大概率在网络 IO
|
100
aeof 219 天前 1
编程小白,能帮忙看看实现有什么问题不: https://aeof.tech/posts/demo/multi-thread-downloader/
|