
版权与内容来源声明本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容均在附表 A 中标注来源引用官方原文保持原样不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式也不对任何收益结果作承诺。转载请注明出处。一、续传不是一个决定是三个断点续传被压缩成一个「分片设多大」的参数实际是三个互不抵扣的决定切多粗、进度记在哪、偏移对不上时听谁的。动其中一个另外两个的毛病一点不少。1.1 断线之后三个问题总被问成一个一个 512 MiB 的文件传到第八次断开日志里八次都从第 0 字节重新开始。群里冒出来的第一句通常是「分片是不是太大了调小点」。这句话只动了第一根轴。如果进度记在进程内存里进程一退就归零分片改成 1 MiB 照样从零开始——重传的字节少了重来的次数一次没少。反过来服务端记了进度、客户端却按自己算的偏移往上写调分片只是让冲突变得更细碎原来一次撞在 16 MiB 的边界上改完撞在 1 MiB 的边界上撞法没变。还有一种更隐蔽的客户端把进度写进本地文件服务端以为自己收了多少就是多少两边各记一份谁也没错续传过去对不上客户端把「本地记录比服务端大」解释成「服务端丢数据」于是整份重传。三种毛病落在三个不同的决定上调参调不出来。1.2 三个决定三本不同的账把「续传」这个词拆开三根轴的计量单位根本不一样粒度回答「一次失败丢多少字节」进度回答「断线后听谁的」冲突处理回答「对不上时谁让步」。这是三个问题不是三个参数。第一根轴花的是时间预算分片越小单次失败损失的字节越少但请求次数按比例涨每个请求都要一次往返。第二根轴花的是状态归属进度存在谁手里断线后就得听谁的代价是一次查询或多一份本地文件。第三根轴花的是冲突策略的成本偏移对不上时是覆盖、是拒绝、还是整块丢掉它决定的是一次断线损失多少字节而不是损失多少次。三本账分别以秒、次、字节为单位。这就是为什么「分片调小点」这句话听起来对症实际上只治第一本账。1.3 把最小模型钉死读数才有意义后面所有数字都来自同一个模型先把假设写清楚否则读数没法比。文件 512 MiB带宽 20 MiB/s单次请求往返 60ms全文件均匀分布 8 次中断每次中断损失半个分片——中断点落在分片内部已传部分作废进度只记得到分片边界所以平均损失半个分片。这是成本模型的模拟不是抓包也不来自某一次真实上传。带宽和往返都取固定值是为了把「重传字节」与「请求次数」两种成本分开看一旦带宽随时间段抖动两组数就会混在一起看不出是谁在花钱。模型里只有一个变量分片大小。其余五个参数全程不动。二、分片越小越省重传也越贵1 MiB 比 16 MiB 多花 26 秒分片小省的是重传字节付的是往返次数两头都不便宜。六档里最快的是 16 MiB30.7 秒最慢的是 1 MiB56.5 秒差 25.8 秒。2.1 实测六档分片两端都不便宜 实测环境Python 3.13.12 / macOS (darwin) / 仅标准库math random# -*- coding: utf-8 -*-断点续传的分片大小账512MB 文件、8 次随机中断比较重传量与请求开销。importmathimportrandom random.seed(20261102)SIZE512*1024*1024# 512 MiBBW20*1024*1024# 20 MiB/sRTT0.060# 单次请求的往返 60msINTERRUPTS8cutssorted(random.uniform(0,SIZE)for_inrange(INTERRUPTS))print(文件 %d MiB带宽 %d MiB/sRTT %dms中断 %d 次均匀分布在文件里%(SIZE//2**20,BW//2**20,RTT*1000,INTERRUPTS))print(%-10s %-10s %-12s %-12s %-12s %-10s%(分片,请求数,重传量,总字节,传输耗时,总耗时))bestNoneformbin(1,2,4,8,16,32):chunkmb*1024*1024reqmath.ceil(SIZE/chunk)# 每次中断平均损失半个分片中断点落在分片内部已传部分作废wastedINTERRUPTS*chunk/2total_bytesSIZEwasted txtotal_bytes/BW overheadreq*RTT tottxoverhead bestmin(bestor(tot,mb),(tot,mb))print(%-11s %-11d %-13s %-13s %-13s %-10s%(%d MiB%mb,req,%.1f MiB%(wasted/2**20),%.1f MiB%(total_bytes/2**20),%.1f s%tx,%.1f s%tot))print(\n最小总耗时出现在 %d MiB 分片%.1f s%(best[1],best[0]))print(分片再小省下的重传量抵不过多出来的往返%d MiB 分片的请求数是 %d MiB 的 %d 倍%(1,8,(SIZE//2**20)//8))文件 512 MiB带宽 20 MiB/sRTT 60ms中断 8 次均匀分布在文件里 分片 请求数 重传量 总字节 传输耗时 总耗时 1 MiB 512 4.0 MiB 516.0 MiB 25.8 s 56.5 s 2 MiB 256 8.0 MiB 520.0 MiB 26.0 s 41.4 s 4 MiB 128 16.0 MiB 528.0 MiB 26.4 s 34.1 s 8 MiB 64 32.0 MiB 544.0 MiB 27.2 s 31.0 s 16 MiB 32 64.0 MiB 576.0 MiB 28.8 s 30.7 s 32 MiB 16 128.0 MiB 640.0 MiB 32.0 s 33.0 s 最小总耗时出现在 16 MiB 分片30.7 s 分片再小省下的重传量抵不过多出来的往返1 MiB 分片的请求数是 8 MiB 的 64 倍两端的形态不一样。左边 1 MiB 每档都在付往返512 个请求光往返就 30.7 秒重传量只有 4.0 MiB。右边 32 MiB 反过来请求只剩 16 个往返压到 1.0 秒可重传量涨到 128 MiB传输本身拖到 32.0 秒总耗时又爬回 33.0 秒。中间三档挨得很近4 MiB 34.1 秒8 MiB 31.0 秒16 MiB 30.7 秒。2.2 那 26 秒从哪来往返多付 28.8 秒带宽反而省 3.0 秒把总耗时拆成传输与往返两段1 MiB 的钱花在哪里一眼能看出来。分片重传量传输耗时请求数往返耗时总耗时比最优多1 MiB4.0 MiB25.8 s51230.7 s56.5 s25.8 s2 MiB8.0 MiB26.0 s25615.4 s41.4 s10.7 s4 MiB16.0 MiB26.4 s1287.7 s34.1 s3.4 s8 MiB32.0 MiB27.2 s643.8 s31.0 s0.3 s16 MiB64.0 MiB28.8 s321.9 s30.7 s0.0 s32 MiB128.0 MiB32.0 s161.0 s33.0 s2.3 s从 16 MiB 换到 1 MiB重传量从 64 MiB 降到 4.0 MiB传输耗时省下 3.0 秒往返次数从 32 次涨到 512 次往返耗时从 1.9 秒涨到 30.7 秒多付 28.8 秒。一省一付净多 25.8 秒四舍五入就是标题里那 26 秒。所以「1 MiB 分片更省钱」这个印象只在带宽那半边成立而且那半边只值 3 秒。多出来的时间几乎全是往返每个请求等一次 60ms 回包乘 480 次。2.3 平台期比最优点更值得记16 MiB 不是唯一的好答案8 MiB 只慢 0.3 秒。8 到 16 MiB 之间是一条平段落在这条段里选哪个都对这时候该用别的约束来定客户端给分片缓冲留多少内存、服务端单次请求的超时、单次失败最多能接受丢多少字节。往右越过 16 MiB 就开始回涨因为重传量与分片大小成正比8 次中断每次丢半个分片16 MiB 分片丢 64 MiB32 MiB 分片丢 128 MiB多出来的 64 MiB 要花 3.2 秒传。往左越过 8 MiB 也在涨因为请求数与分片大小成反比每翻一倍多付一次往返的线性成本。两条斜线在中间交叉交叉点附近就是平台期。平台期不是一个精确值是一段区间。找精确最优值意义不大认准「两头都别越界」才是有用的那条。三、5 MiB 合法不等于你的文件能切 5 MiB协议允许的最小分片和你的文件允许的最小分片是两件事。对象存储的分片下限是 5 MiB但 1 万片的数量上限会把这条下限按文件大小重新抬一遍。3.1 三条硬线官方写得很直白S3 的分片上传限制页把三个数字并排列着Part size 5 MiB to 5 GiB. There is no minimum size limit on the last part of your multipart upload.Maximum number of parts per upload 10,000Maximum object size 48.8 TiB还有一句是建议不是硬线但比硬线更容易被忽略In general, when your object size reaches 100 MB, you should consider using multipart uploads instead of uploading the object in a single operation.三个数字的关系值得算一遍5 GiB × 10000 48.8 TiB正好等于第三个数字。也就是说对象大小上限不是独立的一个数它是「最大分片 × 最大片数」的乘积。这条乘法关系说明片数与片大小是绑在一起的两个刻度动一个必然挤压另一个。约束官方数值对 512 GiB 文件意味着单个分片大小5 MiB 到 5 GiB最后一片不受下限限制下限 5 MiB 合法但只是单片的合法单次上传分片数最多 10,0005 MiB 切成 104857 片直接越线单个对象大小48.8 TiB512 GiB 远没到顶卡住的是片数不是总量何时该用分片对象到 100 MB 就该考虑512 MiB 早过了这条线3.2 1 万片把下限从 5 MiB 抬到 52.4 MiB 实测环境Python 3.13.12 / macOS (darwin) / 仅标准库math randomSIZE_MIB,MAX_PARTS512*1024,10_000print(512 GiB 文件、1 万片上限 → 每片至少 %.1f MiB%(SIZE_MIB/MAX_PARTS))print(按 5 MiB 切 → %d 片%s%(SIZE_MIB/5,在上限内ifSIZE_MIB/5MAX_PARTSelse超过 1 万片上限))print(按 64 MiB 切 → %d 片%(SIZE_MIB/64))512 GiB 文件、1 万片上限 → 每片至少 52.4 MiB 按 5 MiB 切 → 104857 片超过 1 万片上限 按 64 MiB 切 → 8192 片三行输出正好是三种处境。第一行是反推出来的真下限524288 MiB 除以 10000得 52.4 MiB。第二行是最容易踩的坑5 MiB 在协议里完全合法落到 512 GiB 的文件上就是 104857 片超上限十倍不止上传到一半才会被拒。第三行是留了余量的取值64 MiB 切 8192 片离 1 万还剩 1808 片。这里的对照就是那句话法律允许不等于你的文件允许。5 MiB 是协议给单片开的口子1 万片是协议给整次上传设的闸门两个规则同时生效时起作用的是更紧的那个。3.3 先定片数再算片大小把上面的推导写成一行合法区间 [max(5 MiB, ⌈对象大小 ÷ 10000⌉), 5 GiB]。这条式子的用法是从右往左算对象大小除以 1 万得到最小分片再和 5 MiB 取较大值然后看有没有超过 5 GiB。512 GiB 算出来是 52.4 MiB落在区间里如果对象再大一倍到 1 TiB下限就抬到 104.9 MiB仍然没顶到 5 GiB 上限。两个例外要记住。最后一片不受 5 MiB 下限约束小文件不必为了凑整把最后一片撑大。48.8 TiB 是对象上限不是分片上限把这两个数混起来会算出一个荒唐的分片大小。100 MB 那句是「该考虑」的门槛不是禁令小文件走单次 PUT 反而更快——单次请求的往返只付一次。分片上传参数速查表S3 与 tus 两套协议的分片上下限、片数上限、偏移头字段列成一页改参数前先对一遍。放在资料包里扫码即可获取四、进度记在哪决定断线后谁说了算三种记法不是精度差别是断线后谁有资格宣布「已经到哪」的差别。记在客户端客户端说了算记在服务端会话服务端说了算记在存储侧的 part 列表就只剩「哪些分片号已经收到」这一份事实。4.1 记在客户端便宜但只在进程活着时算数客户端本地记进度是最省的做法上传前读一次本地文件拿到上次写到第几字节从那里接着发。零额外请求零服务端改造。它的边界也很清楚。换一台机器、清了缓存、重装一次客户端进度就归零更麻烦的是客户端和服务端各有一份记录两边一旦分叉客户端没有任何办法证明自己是对的。所以本地记录适合「断点只在本机、本会话内有效」的场景桌面端的续传下载、单机跑的离线导入任务。一旦上传变成多端接力或者需要跨设备恢复本地记录就从省事变成本钱——你得再写一套同步逻辑去对齐两份记录而那套逻辑的复杂度比直接问服务端高。4.2 记在服务端会话每次续传先问一次tus 协议把这件事定成了一条规矩偏移是服务端的事实客户端每次续传前先问。TheUpload-Offsetrequest and response header indicates a byte offset within a resource. The value MUST be a non-negative integer.If the offsets do not match, the Server MUST respond with the409 Conflictstatus without modifying the upload resource.The Server MUST acknowledge successfulPATCHrequests with the204 No Contentstatus. It MUST include theUpload-Offsetheader containing the new offset.三句话连起来就是一次完整的续传往返HEAD 问偏移PATCH 带同一个偏移发数据服务端回 204 并把新偏移写回响应头。偏移不符就 409且不改动资源——这一点很关键它保证「冲突」不会变成「写坏」。⚠️代码待验证# 断线后先问服务端「我到哪了」再决定从哪发curl-sI-XHEAD https://storage.example.com/files/9f3a2c\-HTus-Resumable: 1.0.0# → HTTP/1.1 200 OK# Upload-Offset: 268435456# Upload-Length: 536870912# 带上同一个偏移继续发服务端才会接curl-s-XPATCH https://storage.example.com/files/9f3a2c\-HTus-Resumable: 1.0.0\-HUpload-Offset: 268435456\-HContent-Type: application/offsetoctet-stream\--data-binary chunk.bin# → HTTP/1.1 204 No Content# Upload-Offset: 285212672268435456 是 256 MiB285212672 是 272 MiB差值正好一片 16 MiB。客户端不需要记住任何东西只要每次发之前 HEAD 一下。代价是每次续传多一个往返——60ms相对于一次断线丢掉的半个分片这个价格可以忽略。4.3 记在存储侧 part 列表分片号可乱序合并时才定稿S3 的分片上传又是另一套记法进度不是一个字节偏移而是一组 (part number, etag)。每个 part 独立上传、独立重传编号可以乱序到达同一个编号重传会覆盖前一次收尾时 CompleteMultipartUpload 提交一份 part 列表服务端按编号顺序拼起来。这种记法换来了两件事并行上传多个 part重传只影响一个 part。代价是「进度」这个概念变模糊了——断线之后要知道哪些 part 还在得先 ListParts 拿一份清单这是又一次请求而整体成败要到 complete 那一刻才定某个 part 中途丢了complete 会直接报错前面传的全白费。选它还是选偏移式看你要不要并行。串行单流用 tus 那种偏移式更省心多线程分片、断点粒度按 part 算S3 这种 part 列表更自然。五、偏移对不上时怎么处理比分片多大更影响断线代价偏移冲突的处理方式决定断线时丢的是半个分片还是整次会话。三种处理方式的价格差一次中断的量级跟分片大小无关跟你怎么处理冲突有关。5.1 三种处理代价差一次中断重传对齐是按服务端说的偏移重发客户端把本地未确认的那一段丢掉从 HEAD 拿到的偏移开始一个字节不差地接上。丢的是「本地以为传了、服务端没收到」的那一截平均半个分片。409 拒绝是服务端拒收、资源不动客户端拿到冲突重新 HEAD重新算偏移再发。多付一个往返但绝不会把已传数据写坏。tus 那句without modifying the upload resource就是这条约束的原文——先保证不写坏再谈效率。整片丢弃重来最好写冲突了就把这一片从头再传一遍。代码最简单代价也最直白——一个完整分片。分片 16 MiB 时一次冲突丢 16 MiB正好是重传对齐的两倍。而且这个代价会随分片大小线性涨等于把你辛苦调大的分片又按比例还了回去。三种处理不是精度递进是三种不同的取舍对齐省字节但要算409 省正确性但要一次往返整片丢弃省代码但要一片字节。5.2 三轴摊成一张表选型变成查表三根轴各自有几档摊开之后选型就不是「凭感觉」而是对条件。坐标轴取值档换来的付掉的什么条件选它粒度小1–2 MiB单次中断只丢半片重传量低请求数成倍涨往返吃掉全部收益带宽极贵、字节按量计费、断线极频繁粒度中8–16 MiB两种成本都不冒头处于平台期单次失败最多丢 8 MiB默认取值100 MB 到几 TB 的对象都适用粒度大32 MiB 以上请求数极少往返可忽略重传量与分片同比例涨单片失败代价高内网高带宽、链路稳定、片数上限压得死进度客户端本地零额外请求换机即失效两份记录会分叉单机任务、断点不跨会话进度服务端会话偏移只有一个权威来源每次续传多一个往返多端接力、需要跨设备恢复进度存储侧 part 列表可分片并行、重传只影响一片要 ListParts、complete 才知道成败多线程上传、分片天然按编号组织偏移冲突重传对齐只丢半个分片客户端要会和 HEAD 返回的偏移对齐分片偏大、字节成本敏感偏移冲突409 拒绝绝不写坏已传数据一次往返加一次重算多人或多端可能同时写的场景偏移冲突整片丢弃代码最短一次冲突丢一整个分片分片本来就小、断线少见九行里没有一行是「都对」的。每一行的「付掉的」那一列就是你不选它的理由。5.3 四条判断与两条停手条件照条件对号不要照直觉对号。对象不到 100 MB别上分片单次 PUT。往返只付一次分片的那套状态管理全是净支出。对象在 100 MB 到几 GB分片取 8–16 MiB进度记服务端会话。片数自然落在几十到几百离 1 万上限很远平台期也正好在这段。对象上到几十 GB 或更大先算 ⌈大小 ÷ 10000⌉把分片大小抬到这条线下限以上再和 16 MiB 取大。512 GiB 的下限是 52.4 MiB取 64 MiB 刚好留出余量。断线极频繁移动网络、跨地域长链路粒度往 8 MiB 靠冲突用 409 拒绝加服务端偏移。断线稀少、内网稳定粒度往 16 MiB 靠进度记本地就够。停手条件两条。第一条单次失败可能丢掉的字节已经超过你能接受的重传预算就说明分片该往下调别再为了省几个往返往上加。第二条分片继续调小、总耗时不再下降说明瓶颈已经从往返转成带宽或服务端限速再调就是空转——本例里 8 MiB 到 16 MiB 只差 0.3 秒就是这条边界的样子。一句话收口分片大小定在平台期进度记在有权威的那一端冲突处理选那个「绝不写坏」的。三个决定分开定续传才不会在第八次断线时从头再来。tus 与 S3 分片上传对照卡两套协议的偏移头、状态码、片数与片大小限制并排一页接 SDK 前先核一遍。放在资料包里扫码即可获取附表 A本文引用事实与出处对照表事实出处一手来源本文位置Part size 5 MiB to 5 GiB. There is no minimum size limit on the last part of your multipart upload.《Multipart upload and permissions》Amazon S3 用户指南https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html第 3 章Maximum number of parts per upload 10,000Maximum object size 48.8 TiB同上https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html第 3 章In general, when your object size reaches 100 MB, you should consider using multipart uploads instead of uploading the object in a single operation.同上https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html第 3 章The Upload-Offset request and response header indicates a byte offset within a resource. The value MUST be a non-negative integer.《Resumable upload protocol 1.0.x》tushttps://tus.io/protocols/resumable-upload第 4 章If the offsets do not match, the Server MUST respond with the 409 Conflict status without modifying the upload resource.同上https://tus.io/protocols/resumable-upload第 4 章、第 5 章The Server MUST acknowledge successful PATCH requests with the 204 No Content status. It MUST include the Upload-Offset header containing the new offset.同上https://tus.io/protocols/resumable-upload第 4 章512 MiB 文件、20 MiB/s 带宽、60ms 往返、8 次均匀中断、每次损失半个分片下1/2/4/8/16/32 MiB 分片的请求数 512/256/128/64/32/16重传量 4.0/8.0/16.0/32.0/64.0/128.0 MiB总耗时 56.5/41.4/34.1/31.0/30.7/33.0 秒本文实测脚本 work/exp1102/resume_chunk.py取数日期 2026-11-02第 2 章512 GiB 文件、1 万片上限下每片至少 52.4 MiB按 5 MiB 切为 104857 片、超过上限按 64 MiB 切为 8192 片本文实测脚本 work/exp1102/resume_chunk.py 附加片段取数日期 2026-11-02第 3 章口径分片表来自成本模型不是抓包。带宽与往返取固定值中断位置用固定随机种子生成只用于比较不同分片大小的相对差异真实网络下两者都会抖动平台期的位置会平移但「两头都贵、中间平坦」的形状不变。写在最后这篇用到的资料写这篇文章时把 S3 的分片上传限制页和 tus 协议正文翻出来逐句核对又写了个小脚本把 512 MiB 文件断八次的成本算了一遍顺手也整理了几份配套的东西大模型学习路线图从零基础到能自己动手做 Agent按阶段说明每一步该学什么、哪些可以先跳过《LangChain LangGraph MCP 智能体开发实战》视频课7 个模块从私有化部署、EmbeddingRAG 到 MCPAgent 全流程AI 大模型知识库在线可查Agent Skills 从入门到落地、Claude Skills 完全指南等专题按目录浏览即可640 套 AI 大模型行业报告 经典 PDF 书籍看行业落地案例和别人怎么做的时候用得上大模型零基础到精通教学视频跟着敲一遍比只读文档快得多资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「AI」优先通过。资料按「先路线、再动手、最后查漏」的顺序整理好了建议先看学习路线那一份照着它挑一条适合自己当前基础的路径再往下看。