首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >COS 断点续传如何避免拼出混合文件:用 If-Match 锁定对象内容

COS 断点续传如何避免拼出混合文件:用 If-Match 锁定对象内容

原创
作者头像
一只特立独行的兔先生
发布于 2026-10-21 21:37:54
发布于 2026-10-21 21:37:54
190
举报

结论:断点续传要续同一份对象,而不是只续同一个路径

大文件下载到一半断线,最直观的实现是读取本地文件大小,再用 Range 请求剩余内容。但对象路径不是内容的永久标识:如果下载期间有人覆盖了同名对象,前半段来自旧文件、后半段来自新文件,最终长度甚至可能完全正确。

可靠的续传至少需要三个条件:对象身份一致、返回区间正确、实际收到的字节数正确。本文以直连腾讯云 COS 的普通二进制对象为例,使用 If-Match 约束对象身份,先把响应放进独立临时文件,校验后才追加。

1. 先确定协议边界

根据 COS 的 GET Object 官方文档:

  • Range 使用从零开始、包含首尾的字节偏移,只支持单一区间。
  • 请求部分内容时返回 206 Partial Content,并带有 Content-Range。
  • If-Match 指定的值与当前对象 ETag 不一致时,返回 412 Precondition Failed。
  • 启用版本控制后,可以通过 versionId 请求参数下载指定版本;不指定时下载最新版本。

例如 bytes=8-14 一共是 7 个字节,而不是 6 个。Content-Range: bytes 8-14/16 中的 16 是完整对象大小,不能用这次响应的 Content-Length: 7 代替。

ETag 在这里作为条件请求的校验标识使用,不应把任意对象的 ETag 都当成本地文件 MD5。若业务要求确认下载的是某个确切历史版本,应使用 versionId,并确保该版本仍然可访问。

2. 用小对象观察完整过程

准备一个专用测试对象,内容为 0123456789abcdef,正好 16 字节,没有换行。不要使用生产对象做覆盖实验。

以下命令在 Bash 中执行。URL 填写该测试对象可读取的 HTTPS 地址;私有对象使用有效的 GET 预签名 URL,不要为了实验开放整个存储桶。签名若约束了请求头,签名时的头部必须与实际请求一致。URL 包含临时凭证,不能写入工单或公开日志。

代码语言:bash
复制
# 读取前 8 字节,只写测试目录下的新文件
curl --silent --show-error --fail \
  --header 'Range: bytes=0-7' \
  --dump-header first.headers \
  --output first.part \
  "$URL"

继续前先核对:命令成功、状态码为 206、Content-Range 为 bytes 0-7/16、文件长度为 8。记录响应中的 ETag 原始值,包括双引号。如果实际响应不同,就停止,不要把它当成可续传的首段。

接着把拿到的 ETag 填入环境变量,申请第二段:

代码语言:bash
复制
# 将实际 ETag(含双引号)赋给 ETAG 后执行
curl --silent --show-error --fail \
  --header "If-Match: $ETAG" \
  --header 'Range: bytes=8-15' \
  --dump-header next.headers \
  --output next.part \
  "$URL"

本文不展示虚构的云端执行结果。预期是:对象未变化时收到 206、区间为 bytes 8-15/16、临时文件为 8 字节。满足全部条件后,才可以在测试目录合并:

代码语言:bash
复制
cat first.part next.part > result.bin
printf '0123456789abcdef' > expected.bin
cmp expected.bin result.bin

cmp 无差异退出才表示该测试对象逐字节一致。上面的命令是协议实验,不是可直接用于生产的自动续传器:不能省略中间的头部和长度检查。

3. 覆盖实验比成功下载更有价值

重新保留首段,然后将远端测试对象覆盖为 ABCDEFGHIJKLMNOP,长度仍然是 16 字节。继续携带旧 ETag 请求第二段。

如果 ETag 已改变,预期返回 412。此时不要追加任何内容,也不要去掉 If-Match 再试;应废弃旧续传状态,重新获取对象元数据并从头开始。保留或删除旧临时文件可由业务决定,但绝不能把它标记成完成。

这个实验验证的是“拒绝混合版本”,不是“请求最终成功”。只比较最终长度,会漏掉这种同长度覆盖。

还应检查两个失败分支:

  • 收到 200:可能获得的是完整对象,不能直接追加到旧文件后面。走单独的完整下载流程,重新验证后替换。
  • 收到 416:不能仅凭这个状态认定已经下载完成;可能是远端变小、本地偏移错误。重新核对对象身份与总长度。

4. 把实验整理成续传状态机

生产实现需要持久化稳定的对象定位信息、ETag、总长度和已确认写入的偏移。有版本控制时,同时保存 versionId。不要把会过期的预签名 URL 当作唯一对象身份;更新授权不等于允许切换对象版本。

每次请求的处理顺序应当是:

  1. 根据已确认偏移构造单一区间,并带上固定 ETag 的 If-Match。
  2. 将响应写入独立分段临时文件,不直接写入最终文件。
  3. 核对状态码、区间起止、总长度、ETag 以及实际分段字节数。
  4. 校验通过才提交这一段;提交失败不能推进已确认偏移。
  5. 全部段完成后核对总长度;有可信的整文件校验值时再校验,最后原子替换目标文件。

崩溃恢复也要维护这个顺序:如果数据已经追加、偏移记录却尚未更新,重启后不能直接从文件末尾继续。应依据持久化提交点处理未确认尾部,或者保存独立分段及提交清单。否则网络层正确,磁盘恢复仍可能产生重复字节。

5. 注意中间层与成本

上述实验面向 COS 直连地址。不使用自动跟随重定向,也不启用自动解压;代理转码、压缩转换或缓存策略可能改变返回内容与字节偏移的关系。若经由 CDN 或业务网关下载,必须针对实际链路重新验证 Range 和条件请求行为。

排障时保留状态码、Content-Range、ETag 和 x-cos-request-id,但不要记录完整签名 URL。下载实验会产生请求与流量,费用以实际配置和购买页为准。

真正的完成条件不是“重试后没有异常”,而是“每个已提交字节都属于预期对象,并且最终内容通过校验”。

官方参考

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 结论:断点续传要续同一份对象,而不是只续同一个路径
  • 1. 先确定协议边界
  • 2. 用小对象观察完整过程
  • 3. 覆盖实验比成功下载更有价值
  • 4. 把实验整理成续传状态机
  • 5. 注意中间层与成本
  • 官方参考
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档