关于断点续传 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
humbass
V2EX    Node.js

关于断点续传

  •  
  •   humbass 2024-08-22 20:38:13 +08:00 5889 次点击
    这是一个创建于 415 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近一个项目要用到大文件的断点续传,多大文件呢,大的包可能会有 20G ,目前的做法是

    前端取到文件后,按 2m 一个片段进行分片,然后逐个上传 后端收到完整的文件后放在一个隐藏的目录内,等最后的文件传完之后,逐一合并,并移动到指定的文件夹

    实现是实现了,最大的问题不是上传,而是合并以及转移文件相当消耗时间

    有没有更高级的做法呢?

    第 1 条附言    2024-09-01 10:41:45 +08:00
    感谢 @25 @33 @42 ,最后采用 tus ,基本上傻瓜式接入,完美!!
    vivisidea
        1
    vivisidea  
       2024-08-22 20:48:05 +08:00
    是需要完全要自己实现么?你们项目有没有用对象存储?对象存储 sdk 一般都支持这个分片上传+合并操作的

    https://aws.amazon.com/cn/blogs/compute/uploading-large-objects-to-amazon-s3-using-multipart-upload-and-transfer-acceleration/
    mx1700
        2
    mx1700  
       2024-08-22 20:48:19 +08:00 via Android   6
    移动如果同一硬盘不应该耗时啊
    合并的方式不太对,应该直接创建完整大小的空文件,上传接口拿到的缓冲数据直接写到完整文件的指定位置,不需要合并操作
    humbass
        3
    humbass  
    OP
       2024-08-22 20:51:15 +08:00
    @vivisidea 因为是企业内网网络(含广义上的内网)使用,不能使用第三方的云端存储。
    misoomang
        4
    misoomang  
       2024-08-22 20:55:05 +08:00
    开源的对象存储 miio 是否可以考虑使用搭建
    InDom
        5
    InDom  
       2024-08-22 20:57:54 +08:00   1
    @mx1700 #2 认可 2 楼的思路,分片上传只是在上传过程中分片处理。

    保存时可以分片最后合并,也可以直接写到最终文件的对应位置。

    自己做一下记录都接收到哪些块了,这样也方便协商未完成的部份。
    Ipsum
        6
    Ipsum  
       2024-08-22 21:01:56 +08:00   7
    为什么不能直接按全文件大小在指定位置建立空文件,然后 offset 写实际数据进去呢?设置个 cron 。超过 3 天没传完自动删除。
    MoHen9
        7
    MoHen9  
       2024-08-22 21:03:23 +08:00 via Android   4
    分成很多个小文件?说明你实现的断点续传不对,断点续传是一个大文件分成多个请求,每个请求只传自己分好的那一段数据,比如 20 个 G 分成 20 个请求,每个请求传 1G ,第一个请求传从 0 到 1G ,第二个请求从 1G 到 2G ,以依次类推,服务器也是一个文件,根据请求写入文件对应的位置。请求头会携带一些分段的信息,这个有标准的 header ,好像是 range ,可以搜一下
    scegg
        8
    scegg  
       2024-08-22 21:10:42 +08:00
    1 建立一个临时文件储存区。
    2 上传文件方法分为
    ( 1 )新建临时文件,body 是文件的第一段,post 成功后将数据保存在临时文件区,文件名可以为一个随机 guid ,返回这个 id 。
    ( 2 )附加临时文件,body 是文件的第 N 段,将方法 1 返回的 id 作为 query 参数一并提供,post 成功后将新提供的数据附加在这个 id 指定的文件后。
    3 在原“使用”文件的位置(例如新建文件的方法),增加临时文件 id 参数。将指定临时文件移动到永久存储区。

    客户端流程:
    1 打开一个文件。
    使用“新建临时文件”方法,传送第一块,得到文件 ID ;使用“附加临时文件”方法,并提交文件 ID ,传送后续的块;使用原业务方法,提交文件 ID ,完成从临时文件到永久存储区的转移和使用。
    humbass
        9
    humbass  
    OP
       2024-08-22 21:17:45 +08:00
    to: @mx1700 @Ipsum
    说的是同一个事,待验证是否可以按区块在预先创建的文件上直接写数据

    to: @misoomang
    使用开源对象储存,也是一个不错的方式,之前没想到对象存储也有开源的,像这类东西有点大,没信心是否可以驾驭
    libaili
        10
    libaili  
       2024-08-22 22:15:43 +08:00
    @humbass #3 可以部署 minio
    qdwang
        11
    qdwang  
       2024-08-22 22:20:41 +08:00
    @Ipsum 正解
    rekulas
        12
    rekulas  
       2024-08-22 22:21:03 +08:00   1
    移动文件应该很快,耗时应该是在合并上,增加了额外的 io 时间

    所以最佳方案应该就是预申请空间,然后不同的线程可以在不同的段进行 io 写入不会冲突(如果是单线程写入那就更不会了), 只需要验证分块 hash 正确最后的文件应该就没啥问题了
    renmu
        13
    renmu  
       2024-08-22 23:10:20 +08:00 via Android
    建议检查代码的实现,理论上不会很耗时
    jiangzm
        14
    jiangzm  
       2024-08-22 23:31:32 +08:00
    为啥一定要按分片存储呢, 直接将缓冲不断写入单文件不好吗? 还是说用缓存文件替代缓冲,不管是处理缓冲区(Buffer)还是缓存文件,其实都需要每收到一次请求及时做写入目标文件处理啊。
    humbass
        15
    humbass  
    OP
       2024-08-22 23:33:28 +08:00
    @jiangzm 因为要断点续传,直接写入文件,如何告诉客户端从哪里开始上传?后端精确的获得当前文件大小,然后告诉前端从这个位置开始重新上传?
    Suaxi
        16
    Suaxi  
       2024-08-22 23:36:40 +08:00 via Android
    领导允许用开源对象存储的话,可以参考一下 minio 的分片上传
    salparadise
        17
    salparadise  
       2024-08-23 00:04:24 +08:00
    做过类似断点续传,用的 Oss 分片+合并
    01802
        18
    01802  
       2024-08-23 01:39:13 +08:00 via Android
    用 syncthing 去传也行,可以自建
    guanzhangzhang
        19
    guanzhangzhang  
       2024-08-23 08:33:56 +08:00
    创建大小文件,http range 和你 server 进程 seek 写就行
    linhua
        20
    linhua  
       2024-08-23 08:48:35 +08:00
    @humbass #15 断点续传 和 断点下载是差不多的。一般的做法是 先建立一个 实际文件大小的 占位文件,还有一个 存储当前进度信息的 文本文件
    expy
        21
    expy  
       2024-08-23 08:51:53 +08:00
    预分配一个完整文件,用分块序号和分块大小计算要写入的偏移量,前端上传前先查询,后端返回未上传的分块序号。
    listen2wind
        22
    listen2wind  
       2024-08-23 09:02:04 +08:00
    @humbass #3 minio 是可以的,我们使用过,部署在内网的。
    jorneyr
        23
    jorneyr  
       2024-08-23 09:11:41 +08:00
    4G 左右的文件计算 MD 需要 13+S ,这个耗时前后端都不可避免,需要验证文件的完整性是有必要的。

    至于后端小文件合并成大文件,可以使用一个线程合并,也可以使用多个线程分部分合并,然后继续往上合并。
    后端也可以使用文件内存映射直接写入文件中对应的 fragment ,不把收到的部分写入小文件。

    具体要分析是哪个部分慢,例如是计算 MD5 32 慢,看看是否改成 MD5 16 也能够满足需求。
    lerry
        24
    lerry  
       2024-08-23 09:17:23 +08:00
    有轮子就直接用。
    minio 是 golang 实现的,很容易部署,我是用的 docker ,直接下载一个单文件应该也可以。
    TOUJOURSER
        25
    TOUJOURSER  
       2024-08-23 09:17:44 +08:00   1
    正好前段时间遇到类似的需求,我们采用的是 tus + minio
    shenyansycn
        26
    shenyansycn  
       2024-08-23 10:03:02 +08:00
    放硬盘里,人肉过去传输,最快。
    unknown404
        27
    unknown404  
       2024-08-23 10:15:41 +08:00
    请使用支持 S3 的相关存储(公有云对象存储或者私有云 minio),建议 op 好好重读下 http 的 RFC ,https://datatracker.ietf.org/doc/html/rfc7233#section-4.2 有标准的协议不用,你去造轮子
    Rorysky
        28
    Rorysky  
       2024-08-23 10:15:58 +08:00
    @MoHen9 你说的是多线程下载,和断点续传有什么关系?
    MoYi123
        29
    MoYi123  
       2024-08-23 11:59:49 +08:00
    @unknown404 这是下载, 不是上传.
    nxcdJaNnmyF9O90X
        30
    nxcdJaNnmyF9O90X  
       2024-08-23 12:16:33 +08:00
    minio
    Overfill3641
        31
    Overfill3641  
       2024-08-23 12:22:37 +08:00
    去看看 BT 软件是如何实现就完事了,只是把一对多改成一对一。
    unknown404
        32
    unknown404  
       2024-08-23 13:45:07 +08:00
    @MoYi123 原理一样的
    duglik
        33
    duglik  
       2024-08-23 14:01:19 +08:00   1
    heiya
        34
    heiya  
       2024-08-23 16:00:39 +08:00
    我的做法是:
    1.前端做好分片,为每一个分片生成序号,统计分片的个数。将这些数据传给后端,后端把这些数据记录下来,为这个文件生成一个全局 id ,返回给前端。
    2.前端每次上传分片会连带文件 id+分片 id 一起传过来。分片文件会被上传到一个临时目录。每上传完成一个分片,后端会记录下来分片 id (分片序号)。如果是并发上传的话,要注意已上传分片 id 集合会有线程安全问题,不然会出现某个分片已上传但没记录的问题。
    3.后端返回已上传分片集合。同时会有一个异步线程判断该文件 id 下的分片是否全都上传完毕。如果全都上传完成,调用文件系统 SDK 的合并文件方法(我用的是 minio ),合并完成之后,删除临时文件目录的分片。
    4.与此同时,前端全部分片上传完成之后,循环调用获取文件合并状态接口。
    完成~
    klo424
        35
    klo424  
       2024-08-23 16:49:11 +08:00   1
    @humbass #15
    1. 前端生成一个文件 id ,连带文件 size 、offset 和二进制数据传给后端。
    2. 后端生成一个 size 大小的空文件,根据 id 找到文件,再根据 offset 去寻找位置写入二进制数据。
    3. 后端写完数据返回成功,前端 offset 更新继续传下一个二进制数据给后端,以此循环。
    4. 如果断了,下次打开页面时,从后端获取到未完成的文件 id ,再执行 1-3 过程。
    cstj0505
        36
    cstj0505  
       2024-08-23 16:51:18 +08:00
    其实分片这东西完全是浏览器限制,不用 js,用个流式写入大文件直接边读边写
    yaodong0126
        37
    yaodong0126  
       2024-08-23 16:55:20 +08:00
    好家伙,你们但凡看过断点续传的原理,就说不出什么转移合并的话
    trzzzz
        38
    trzzzz  
       2024-08-23 17:28:11 +08:00   1
    @heiya 透传 minio 的 uploadId 给前端,前端每次上传分片就带上 uploadId 和 partNumber 。服务器收到一片也不用写本地,直接透传到 minio ,我习惯这样。
    但这样在弱网环境下会有问题,简单说个 流的 retry 需要自己写,这个是最麻烦的。如果是弱网还是建议先写 tmp 再异步上传
    trzzzz
        39
    trzzzz  
       2024-08-23 17:36:21 +08:00
    @cstj0505 弱网环境或并发大场景下,流 reset 不好搞(最终还是需要缓存上传的数据)
    ZZ74
        40
    ZZ74  
       2024-08-23 17:43:23 +08:00
    @klo424
    复议 这才是断点续传的最初或者真实的做法。
    OP 可以用这种方式避免重新组合文件。java 这边对应的 radomaccessfile....
    另外为啥要组合?如果服务器不需要读取处理啥的,只是提供存储,直接存分片,下载的时候由前端拼也可以。
    我不懂前端,你这 20G 的文件 前端下载时是否合并的了得打个问号
    jiangzm
        41
    jiangzm  
       2024-08-23 18:29:20 +08:00
    @humbass #15 不需要告诉客户端怎么传,而是客户端怎么传告诉服务端。根据分片的大小,分片的索引可以计算 offset ,有 offset 不就可以直接写入了。 你最后上传完合并不也要确定分片的顺序吗, 在等待的过程完全可以边写边传。
    netnr
        42
    netnr  
       2024-08-23 18:38:24 +08:00   2
    okakuyang
        43
    okakuyang  
       2024-08-23 18:57:04 +08:00 via iPhone
    这些不都是基操吗,检查下自己代码,怕不是有 bug
    hetal
        44
    hetal  
       2024-08-23 21:28:28 +08:00
    rsync 不行么?
    zhaokun
        45
    zhaokun  
       2024-08-24 09:19:47 +08:00
    内网搭建一台开源的对象存储
    zhaokun
        46
    zhaokun  
       2024-08-24 09:22:04 +08:00
    为啥要等都上传完了再合并,可以上传一点合并一点呀,顺序不对的就等着呗,合并完再移?不是直接在目标文件夹下直接合并吗?
    humbass
        47
    humbass  
    OP
       2024-08-24 20:05:54 +08:00
    @netnr 这个倒是新东西,俺研究下看看,thx
    victimsss
        48
    victimsss  
       2024-08-26 09:44:52 +08:00
    --- 因为是企业内网网络(含广义上的内网)使用,不能使用第三方的云端存储。
    第三方和内不内网有什么关系吗,贵司平时不是用支持私有部署的服务吗
    heiya
        49
    heiya  
       2024-08-26 10:35:11 +08:00
    @trzzzz hi ,我还是没 get 到弱网环境下的问题。实际在上传时后端会维护一个 uploadId 下的已上传分片数组,客户端(我这边是 windows 客户端)会根据这个数组尝试重试机制。所以无论网络环境如何、是否是并发上传,只要客户端能正确处理这个数组就可以。
    trzzzz
        50
    trzzzz  
       2024-08-26 21:58:23 +08:00
    @heiya 这个是发生在 server 端,server 端透传走客户端传的流(弱网是在 server 端 到 其它地方,比如对象存储)
    heiya
        51
    heiya  
       2024-08-27 10:01:32 +08:00
    @trzzzz thanks~
    dode
        52
    dode  
       2024-08-27 17:46:53 +08:00
    分块上传,并且要求按顺序上传,服务器顺序写入追加就行了
    dode
        53
    dode  
       2024-08-27 17:47:47 +08:00
    客户端按固定大小分块上传,直至文件结束
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2404 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 01:16 PVG 09:16 LAX 18:16 JFK 21:16
    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