华为云 OBS 对象存储大文件分段上传失败与断点续传失效:一次 2TB 备份任务卡在 Part 5003 的完整排查

举报
yd_260132607 发表于 2026/11/09 10:07:39 2026/11/09
【摘要】 华为云 OBS 对象存储大文件分段上传失败与断点续传失效:一次 2TB 备份任务卡在 Part 5003 的完整排查一、问题现场我们有台备份服务器,每天凌晨两点把当天的日志与数据库快照打包后上传到华为云 OBS 的一个归档桶里。桶用的是 OBS 官方 SDK 的分段上传(multipart upload),分片大小 8MB,并发度设成 16。整套流程跑了半年多,一直稳定。上周三开始,任务开始...

华为云 OBS 对象存储大文件分段上传失败与断点续传失效:一次 2TB 备份任务卡在 Part 5003 的完整排查

一、问题现场

我们有台备份服务器,每天凌晨两点把当天的日志与数据库快照打包后上传到华为云 OBS 的一个归档桶里。桶用的是 OBS 官方 SDK 的分段上传(multipart upload),分片大小 8MB,并发度设成 16。整套流程跑了半年多,一直稳定。

上周三开始,任务开始不稳定。前两天只是偶尔重试一次还能成功,到后面变成每天都失败,日志里的报错是 `EntityTooLarge: Your proposed upload exceeds the maximum allowed size` 和 `NoSuchUpload: The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted.` 交替出现。更奇怪的是,同样的分片大小和并发配置,别的几个桶一切正常,只有这个桶不行。

任务卡住的位置很固定:分片序号大约在 5000 附近,也就是文件上传到 40GB 左右的时候。5003 这个数字稳定得让人怀疑是某个阈值。

二、第一层排查:先确认是服务端拒绝还是客户端没发对

第一反应是配额问题,于是先去控制台和 API 侧确认:`ListMultipartUploads` 能列出进行中的分段上传,`ListParts` 能看到已经上传成功的分片,服务端侧的分片状态是正常的。所以不是整个上传被服务端中止,而是某个分片在重试过程中出了问题。

接下来看 SDK 侧的错误上下文。这里有个坑:一开始我们只打印了异常类型和 message,没有打印分片号、uploadId、请求的 Content-Length 和实际收到的字节数,于是所有错误看起来都长得一样。补上这些字段之后,错误被分成了两类:

第一类是 `EntityTooLarge`,对应的 Content-Length 明显大于分片大小,属于 SDK 重试时把整个分片重新组装了一遍;第二类是 `NoSuchUpload`,uploadId 是有效的但服务端说找不到,说明这个上传已经被 abort 了。

这两类错误混在一起,让最初的几轮排查完全失去了方向。

三、第二层排查:把客户端分片和服务端分片一一对上

改用 SDK 的低层接口自己管理分片,不依赖高层封装:先 `CreateMultipartUpload`,然后对每个分片自己算 offset,调 `UploadPart`,并把每次的返回(PartNumber、ETag、Size)落盘到一张本地表里。然后拿这张表去和 `ListParts` 的结果对比。

对比结果很说明问题:客户端记录的已上传分片是连续的 1..5002,然后直接跳到 5004,5003 缺失;而服务端 `ListParts` 返回的是 1..5002 加上 5004。也就是说,**分片 5003 从来没有真正上传成功过**。之前所有看起来「上传了一半」的进度,都是客户端本地状态给的假象。

四、第三层排查:定位分片 5003 有什么特殊

一个分片永远失败,通常说明这个分片的内容有问题,或者它的偏移量有问题。我们打印了 5003 的详细信息:

  • PartNumber:5003
  • - Offset:41943040(即 5002 × 8MB + 2 个 8MB,也就是 40GB 零头)
  • - 实际 Content-Length:8388608(正好 8MB)
  • - 读文件时的字节数:也是 8388608

看起来完全正常。既然文件内容和长度都对,那问题只能在「怎么发出去」这一步。

于是我们用 curl 直接对同一个文件、同一段字节发了一次 `UploadPart`:

```

curl -X PUT "https://<bucket>.obs.<region>.myhuaweicloud.com/<key>?partNumber=5003&uploadId=<id>" \

  -H "Content-Type: application/octet-stream" \

  --data-binary @part5003.bin

```

结果是 200 OK,ETag 正常返回。也就是说**用同样的数据、同样的接口,SDK 失败而 curl 成功**,问题必然出在 SDK 发请求的环节,而不是数据本身。

五、第四层排查:SDK 与代理链路的差异

我们前面还有一层 Nginx 做上传代理,proxy_request_buffering 开着,body 上限设的是 100MB。对比 SDK 和 curl 的实际请求头,差异集中在两处:

一是 SDK 在重试时走了 `Expect: 100-continue`,curl 不会。Nginx 对带 `Expect: 100-continue` 的请求,会先等上游返回 100 再转发 body,行为和普通请求不一样,retry 逻辑在这里被卡住后,SDK 侧把异常归类成了 body 过大。

二是 SDK 重试时用的是 `Transfer-Encoding: chunked`,而代理层对 chunked 请求的处理路径和 Content-Length 请求不同。

我们做了两个对照实验来确认:

实验一:把代理的 `proxy_request_buffering off`(改成流式转发),不重启业务,只是灰度一台。结果失败率从 100% 掉到 0。

实验二:直连 OBS 域名、不经过代理,SDK 上传完全正常。

两条实验一起说明:根因在代理层对大请求体 + 100-continue 的处理,而不是 OBS 本身,更不是分片大小或并发度。

六、根因与修复

定位到根因之后,修复分了三步:

第一步,去掉代理层的请求体缓冲,`proxy_request_buffering off`,同时把 `client_max_body_size` 提到 0(不限制),让大请求体直接流式转发。

第二步,把 SDK 的分片大小从 8MB 降到 16MB,单分片耗时变长,但因为每个请求体更小地落在一个缓冲窗口内,代理层的稳定性反而明显变好。

第三步,给上传任务补上了真正的断点续传能力。之前我们的续传逻辑有个致命错误:失败后重新 `CreateMultipartUpload` 拿了一个新的 uploadId,然后从第 1 片重新传。正确的做法是复用原来的 uploadId,只重传缺失的分片。改完之后,同一个 2TB 文件的续传从「整文件重传」变成了「只补 5003 这一片」。

七、验证结果

修复后的第一周,同一个 2TB 的备份任务连续七天全部成功,单次上传耗时从平均 96 分钟降到 71 分钟。关键的一点是:我们给每个分片做了 ETag 校验,只有全部 `ListParts` 与本地记录一致才判定任务成功,而不是像以前那样只看 SDK 有没有抛异常。

八、复盘:可以拿走的六条经验

第一,大文件上传失败时,不要一上来就调分片大小和并发度,先把客户端的分片账本和服务端的 `ListParts` 对齐,账本对不上的时候,客户端的进度条是没有参考价值的。

第二,同一份数据用 SDK 失败、用 curl 成功,就说明问题在客户端发请求这一侧,可以直接用 curl 做二分定位。

第三,代理层是排查长尾问题里最容易被忽略的一环。`Expect: 100-continue`、请求体缓冲、chunked 编码这三个组合,值得在任何「SDK 失败但服务端看起来正常」的场景里优先怀疑。

第四,断点续传必须复用同一个 uploadId,并且要有逐分片的 ETag 校验,否则「续传」只是重传。

第五,分片大小不是越小越好也不是越大越好,它应该和链路上最脆弱的那个环节(这里是代理的缓冲窗口)匹配。

第六,不要在证据不足时用「可能是平台问题」收尾。我们在这个问题上浪费了大半天,怀疑过 OBS 限流、怀疑过桶配额、怀疑过 SDK 版本,唯一没怀疑的就是自己部署的那层代理。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。