
CDN 断点续传依赖 HTTP Range 请求。客户端下载中断后,可以使用 Range: bytes=... 只请求尚未完成的字节区间;服务器或 CDN 正确处理时返回 206 Partial Content,并通过 Content-Range 告知本次响应对应的位置。这样无需重新下载整个安装包、视频、模型文件或备份文件。
不过,看到 Accept-Ranges: bytes 并不代表续传一定可靠。还必须确认源站能稳定返回正确字节、文件更新期间不会把不同版本拼接在一起、CloudFront 缓存行为符合预期,并处理压缩、分段并发、签名链接和错误范围等边界情况。
断点续传涉及哪些 HTTP 状态和响应头?
| 项目 | 作用 | 示例 |
|---|---|---|
Range | 客户端请求一个或多个字节区间 | Range: bytes=1048576- |
Accept-Ranges | 服务器提示是否支持范围单位;bytes表示支持字节范围 | Accept-Ranges: bytes |
206 Partial Content | 服务器成功返回所请求的部分内容 | HTTP 状态码 206 |
Content-Range | 说明响应包含的起止字节及完整对象长度 | bytes 1048576-2097151/5242880 |
416 Range Not Satisfiable | 请求范围无法满足,例如起点超过文件结尾 | Content-Range: bytes */5242880 |
If-Range | 只有文件仍与指定验证器一致时才返回范围,否则返回完整新版本 | 强 ETag 或 HTTP 日期 |
ETag/Last-Modified | 帮助客户端确认对象版本,防止续传时拼接新旧内容 | ETag: "version-abc" |
Accept-Ranges只是能力提示。RFC 9110 说明,客户端即使没有看到该响应头也可以尝试范围请求;反过来,即使看到它,也不能假定以后的每次请求都一定返回 206。单区间 Range 请求如何工作?
假设一个文件大小为 5,242,880 字节,客户端下载了前 1 MiB 后断开,可以从下一字节继续请求:
GET /downloads/app.bin HTTP/1.1 Host: download.example.com Range: bytes=1048576-
服务器成功支持范围请求时,典型响应为:
HTTP/1.1 206 Partial Content Accept-Ranges: bytes Content-Range: bytes 1048576-5242879/5242880 Content-Length: 4194304 Content-Type: application/octet-stream ETag: "app-v7"
Range使用包含首尾的字节位置,因此 bytes=0-1023请求 1,024 字节。以下三种写法含义不同:
bytes=0-1048575:请求前 1 MiB。bytes=1048576-:从第 1,048,576 字节请求到文件末尾。bytes=-1048576:请求文件末尾的 1 MiB。
为什么断点续传要配合 ETag 或 Last-Modified?
如果客户端下载了一半,源站随后用同一个 URL 覆盖了文件,再继续按旧位置请求,客户端可能把旧文件前半段与新文件后半段拼接,最终得到损坏文件。稳妥的下载器应保存强 ETag,并在续传时发送 If-Range:
GET /downloads/app.bin HTTP/1.1 Range: bytes=1048576- If-Range: "app-v7"
如果对象仍是 "app-v7",服务器可返回 206;如果验证器不匹配,服务器应忽略 Range 并返回 200 OK和完整的新版本,客户端从头重新下载。If-Range不能使用弱 ETag,因为弱验证器不足以保证字节级一致。
更简单的发布方法是使用不可变版本 URL,例如 /downloads/app-7.3.1.bin,并让旧版本在合理时间内继续可用。这样可以同时提升缓存命中率和续传可靠性。
CloudFront 如何处理 Range GET?
当 CloudFront 收到 Range GET 时,会先检查接收请求的边缘缓存:
如果缓存中已有完整对象或所需区间,CloudFront 直接返回该范围。
如果没有对应内容,CloudFront 将请求转发到源站;为提高效率,它可能向源站请求比客户端所需更大的区间。
源站支持 Range 时,返回部分内容,CloudFront 将相关范围提供给用户并缓存。
源站不支持 Range 时,可能返回完整对象;CloudFront 会向当前用户发送完整对象并缓存,后续范围请求可从完整缓存对象中截取。
AWS 还明确指出,如果查看器发送 Range GET,而源站返回 Transfer-Encoding: chunked,CloudFront 会向查看器返回完整对象,而不是请求的范围。大文件下载源站应优先返回稳定的 Content-Length,并验证实际响应链路中没有被代理改为分块传输。
CloudFront 对多区间请求还有额外要求
CloudFront 一般遵循 HTTP Range 规范,但 AWS 文档列出了多区间的要求:范围必须按升序排列、不能重叠,并且每个范围都必须有效。如果不满足这些条件,CloudFront 可能返回 200和完整对象,而不是 206。
# 合法:升序且不重叠 Range: bytes=0-999,2000-2999 # 不符合 CloudFront 要求:顺序颠倒 Range: bytes=2000-2999,0-999 # 不符合要求:范围重叠 Range: bytes=0-1999,1000-2999
对于下载器,多个并行的单区间请求通常比一个复杂的多区间请求更容易实现和验证,但并发数量必须受控,避免放大连接数、源站请求和失败重试。
超过 CloudFront 最大可缓存对象大小怎么办?
根据 AWS 当前文档,启用缓存时,CloudFront 不会检索并缓存大于 50 GB 的单个完整对象;禁用缓存时可以传递更大的对象,但不会缓存。对于超过该限制的文件,可以由客户端按多个小于 50 GB 的范围请求下载,CloudFront 分别缓存各个部分。
这并不意味着应把每段设置得接近 50 GB。合理分段需要结合失败重试成本、用户网络、源站吞吐、边缘命中、并发限制和客户端内存。软件下载和媒体播放器通常会使用远小于该上限的区间。
压缩为什么容易与 Range 请求冲突?
字节范围针对的是“选定表示”的字节序列。如果对象以 Gzip 或 Brotli 传输,范围位置对应压缩后字节,而不是未压缩文件的位置。AWS CloudFront 文档也明确说明,对压缩对象的 Range 请求以压缩后的大小为基础。
对于 ZIP、MP4、ISO、安装包和多数二进制大文件,它们通常已经压缩或不适合 CDN 自动压缩。建议保持固定的内容编码,不要让同一个可续传 URL 在不同请求中随机切换压缩与未压缩表示。
检查是否错误地对二进制下载启用了动态压缩。
确认
Content-Encoding、Content-Length和ETag与实际表示一致。若存在压缩与未压缩两种表示,缓存键必须正确区分
Accept-Encoding。不要把“文件扩展名相同”当成字节内容完全相同的保证。
CDN 大文件下载缓存如何配置?
使用版本化 URL:文件更新时发布新路径,不直接覆盖正在下载的对象。
提供稳定长度:源站返回正确的
Content-Length,避免 Range 请求被 chunked 响应破坏。保持验证器一致:完整响应与部分响应使用代表同一对象版本的强 ETag。
合理设置 TTL:不可变文件可使用较长 CDN 和浏览器缓存;可覆盖文件应使用更谨慎的缓存策略。
控制缓存键:不要让无关 Cookie、查询参数或请求头把同一个文件拆成大量缓存对象。
保护源站:防止用户绕过 CDN 直接下载,也要避免签名 URL 的无关参数进入缓存键。
限制并发:客户端下载器设置合理的分片大小、最大并发和退避重试。
验证完整性:发布 SHA-256 等校验值,让客户端下载完成后验证整个文件。
如何用 curl 验证断点续传?
1. 查看基础响应头
curl -I https://download.example.com/files/app.bin
检查 Content-Length、ETag、Last-Modified、Accept-Ranges、Content-Encoding和 CDN 缓存状态。
2. 请求前 1,024 字节
curl -sS -D headers.txt \ -H "Range: bytes=0-1023" \ -o part-000.bin \ https://download.example.com/files/app.bin
期望看到 206 Partial Content、Content-Range: bytes 0-1023/总长度以及 Content-Length: 1024。
3. 使用 If-Range 验证版本变化
curl -sS -D headers.txt \ -H "Range: bytes=1048576-" \ -H 'If-Range: "app-v7"' \ -o remainder.bin \ https://download.example.com/files/app.bin
ETag 一致时应返回剩余范围;不一致时应返回 200 和完整新对象。测试脚本必须同时处理两种结果,不能把 200 的完整文件直接追加到旧分片后面。
4. 请求越界范围
curl -sS -D - \ -H "Range: bytes=999999999999-" \ -o NUL \ https://download.example.com/files/app.bin
在 Windows 中可将正文写入 NUL。服务器通常应返回 416 Range Not Satisfiable,并通过 Content-Range: bytes */总长度说明当前对象长度。
上线监控应关注哪些数据?
| 指标 | 异常可能表示 |
|---|---|
| 200 与 206 比例 | 大量范围请求退化为完整下载,或客户端没有正确使用 Range |
| 416 数量 | 客户端保存的偏移过期、文件被替换或范围计算错误 |
| 缓存命中率 | 查询参数、签名参数、Cookie 或分片策略造成缓存碎片 |
| 每次下载的请求数 | 分片过小、并发过高或重试策略失控 |
| 源站出站流量 | 边缘无法复用分片、TTL 太短或 Range 未被正确缓存 |
| 文件校验失败 | 版本混合、源站对象不一致、代理改写或客户端拼接错误 |
日志中还应关联对象版本、请求范围、响应范围、状态码、缓存结果和源站延迟。避免记录签名令牌或用户敏感参数;如 URL 包含凭证,应在日志管道中脱敏并限制访问权限。
常见误区
误区一:只要返回 Accept-Ranges 就支持断点续传
必须实际发送 Range 请求并核对状态码、响应字节和 Content-Range。中间 CDN、反向代理或源站配置都可能让请求退化为完整响应。
误区二:200 和 206 都直接追加到已有文件
206 可以按正确偏移写入;200 通常代表完整表示,应覆盖旧临时文件或重新开始。错误追加会产生体积异常的损坏文件。
误区三:分片越多越快
过高并发会增加连接、TLS、请求费、源站压力和失败重试。现代 HTTP 连接可以复用,但仍应根据真实网络测试确定并发和分片大小。
误区四:覆盖同一 URL 不影响续传
旧分片和新分片可能被拼在一起。使用版本化 URL、强 ETag、If-Range和完整文件哈希共同降低风险。
误区五:签名 URL 一定能支持长时间下载
应核对签名的有效期和产品行为。下载开始后连接中断,再次发起 Range 请求时可能已超过有效期。有效期需要覆盖合理下载窗口,同时不能无限放宽私有文件访问。
常见问题
CloudFront 会缓存 206 响应吗?
CloudFront 可以处理并缓存 Range GET 所需的对象部分,也可能为了优化向源站请求更大的范围。实际命中行为应通过响应头、CloudFront 日志和重复请求测试验证。
源站不支持 Range,CloudFront 能自动补救吗?
源站若返回完整对象,CloudFront 可将完整对象发送并缓存,之后从完整缓存中响应范围请求。但当前首次请求可能收到完整文件,因此源站原生正确支持 Range 更适合大文件业务。
为什么发送 Range 后仍然收到 200?
可能是源站不支持范围请求、请求格式无效、多区间顺序或重叠不符合 CloudFront 要求、If-Range验证失败,或中间代理选择返回完整表示。应从边缘响应一路检查到源站。
Range 请求可以用于视频拖动播放吗?
可以,媒体播放器常使用字节范围跳转读取文件,但视频容器的元数据布局也会影响是否能快速开始和定位。专业流媒体还可能采用 HLS 或 DASH 分片,而不是仅依赖一个大文件的 Range。
每个分片应该多大?
没有适用于所有业务的固定值。需要结合文件大小、用户网络、失败概率、并发数、请求成本、缓存复用和客户端能力测试。应设置上下限并支持动态调整,而不是允许无限小分片。
结论
CDN 断点续传的核心是正确实现 HTTP Range 语义:客户端发送合法范围,服务端或 CloudFront 返回准确的 206 和 Content-Range,并通过强 ETag、If-Range或版本化 URL 保证所有分片属于同一个对象版本。
在 CloudFront 上,还要验证源站 Range 支持、Content-Length、chunked 响应、压缩表示、多区间规则和大对象缓存限制。最后用真实下载器、curl、边缘日志和文件哈希共同测试。只有中断后能安全续传、缓存仍有复用价值、文件最终校验一致,才算真正支持大文件断点续传。
参考资料
AWS:How CloudFront processes partial requests for an object (range GETs),核查日期:2026-08-14
AWS:Serve compressed files,核查日期:2026-08-14
IETF RFC 9110:HTTP Semantics,核查日期:2026-08-14
MDN:HTTP range requests,核查日期:2026-08-14
MDN:If-Range header,核查日期:2026-08-14