分片、断点续传与临时文件:失败传输怎样恢复并清理
断点续传上线后,成功率提高了,存储账单却持续上涨。调查发现大量 uploadId 只有一两个 part,业务数据库早已删除对应记录,对象存储仍保留碎片。另一些客户端重试时重新申请 uploadId,同一业务文件散落成多组 parts。要恢复的是同一个上传会话,不是重新发起一轮看似相同的请求。
S3 multipart upload 由 initiate、UploadPart、CompleteMultipartUpload 与 AbortMultipartUpload 组成;part number 决定最终顺序,同号重传会覆盖旧 part,完成时提交 part/ETag 列表。已发起但未 complete 或 abort 的 parts 会持续占用存储并计费,因此清理不是附加功能。
从请求入口画出真实字节路径
创建会话时生成业务 uploadId,绑定租户、目标 key、预计大小、partSize、checksum 算法、过期时间和状态 INITIATED;对象存储返回 providerUploadId,只保存在服务端。每个 part 使用 uploadId+partNumber 唯一约束,记录 expectedOffset、actualSize、checksum、ETag 与状态。客户端查询账本得到已确认 parts,只重传缺失或校验失败的部分;complete 使用账本快照和版本条件从 UPLOADING 迁移到 COMPLETING,避免两个请求提交不同 part 集合。
关键提交窗口怎样形成
同号 part 可以安全覆盖的前提是载荷 checksum 相同;同号不同内容必须冲突,不能默默接受最后写入。CompleteMultipartUpload 成功但响应丢失时进入 UNKNOWN,先 HEAD 目标 objectVersion 或查询会话,禁止直接重新 complete 或发起新会话。abort 与正在上传的 part 存在竞态:先阻止新租约、等待在途上传结束,再重复 abort 并对账。清理器用过期索引分页领取,保留 fence 防止把刚续租的会话删掉。
六个状态节点逐段落地
创建上传会话
进入“创建上传会话”时记录 fileId/uploadId、objectVersion、实际字节、checksum、owner、状态版本与 deadline。正向实验断言字节、状态和业务引用守恒;反向实验在流关闭、对象提交或状态回写前后制造断连与超时,确认临时资源有归宿、UNKNOWN 能对账、迟到写不能覆盖新版本。
签发 Part 能力
进入“签发 Part 能力”时记录 fileId/uploadId、objectVersion、实际字节、checksum、owner、状态版本与 deadline。正向实验断言字节、状态和业务引用守恒;反向实验在流关闭、对象提交或状态回写前后制造断连与超时,确认临时资源有归宿、UNKNOWN 能对账、迟到写不能覆盖新版本。
校验并记账
进入“校验并记账”时记录 fileId/uploadId、objectVersion、实际字节、checksum、owner、状态版本与 deadline。正向实验断言字节、状态和业务引用守恒;反向实验在流关闭、对象提交或状态回写前后制造断连与超时,确认临时资源有归宿、UNKNOWN 能对账、迟到写不能覆盖新版本。
恢复缺失分片
进入“恢复缺失分片”时记录 fileId/uploadId、objectVersion、实际字节、checksum、owner、状态版本与 deadline。正向实验断言字节、状态和业务引用守恒;反向实验在流关闭、对象提交或状态回写前后制造断连与超时,确认临时资源有归宿、UNKNOWN 能对账、迟到写不能覆盖新版本。
原子 Complete
进入“原子 Complete”时记录 fileId/uploadId、objectVersion、实际字节、checksum、owner、状态版本与 deadline。正向实验断言字节、状态和业务引用守恒;反向实验在流关闭、对象提交或状态回写前后制造断连与超时,确认临时资源有归宿、UNKNOWN 能对账、迟到写不能覆盖新版本。
过期 Abort 清理
进入“过期 Abort 清理”时记录 fileId/uploadId、objectVersion、实际字节、checksum、owner、状态版本与 deadline。正向实验断言字节、状态和业务引用守恒;反向实验在流关闭、对象提交或状态回写前后制造断连与超时,确认临时资源有归宿、UNKNOWN 能对账、迟到写不能覆盖新版本。
文件不是一个字段,而是一条跨资源事务
流式只成立在所有缓冲都有上限时
完整性、安全性和业务有效性是三条证据
下载也是受控数据流
观测与对账要覆盖数据库和对象存储两边
核心指标包括 receiving bytes、active streams、temporary bytes、multipart age、quarantine age、scan result、unknown operations、orphan candidates、delete lag 与 egress bytes。高基数 fileId/uploadId 进入日志和 Trace,指标只保留租户等级、状态和错误分类。容量告警同时观察堆、直接内存、临时盘 inode/bytes、连接池、出口带宽和对象存储限流。
发布和回滚必须保持对象格式与状态兼容
反向实验要击中资源与状态的交界
partSize 过小会制造海量请求、元数据和 ETag,过大又让失败重传成本上升;并发过高会吃满客户端连接、出口带宽和存储限流。临时目录与对象存储临时前缀是两套容量,应用不能只监控最终 bucket。清理成功率要按扫描、领取、abort、确认释放分段统计,单看 delete API 返回成功会漏掉竞态中的在途 parts。 实验固定文件字节、并发、缓冲、存储版本和故障点,保存容器临时目录、对象元数据、数据库状态与连接水位。修复后复用相同样本,既证明错误被拒绝或恢复,也证明堆、临时盘、parts 和连接回到稳定基线。
两个 Java 17 模型固定量化见证
模型执行真实计算和断言,把框架日志容易掩盖的流量或状态不变量压缩成确定输出。
javac --release 17 -Xlint:all -Werror examples/backend-development/file-storage/multipart-resume-temp/PartLedgerDemo.java examples/backend-development/file-storage/multipart-resume-temp/TempCleanupDemo.java
java -cp examples/backend-development/file-storage/multipart-resume-temp PartLedgerDemo
java -cp examples/backend-development/file-storage/multipart-resume-temp TempCleanupDemopartsExpected=4 delivered=5 uniqueParts=4 sameReplay=1 conflictingReplay=0 completeAllowed=true
sessions=6 expired=3 leased=1 aborted=2 skippedRenewed=1 leaked=0 cleanupSafe=true工程验证再替换成真实 Servlet、对象存储、解析器与数据库,保留相同 fileId/uploadId、状态迁移和输出不变量;模型字符串不能替代实际断连、覆盖、扫描和清理实验。
架构选择由数据流和授权新鲜度决定
网络稳定且对象较小的上传使用单 PUT 最简单;大对象或弱网使用 multipart;需要客户端直传时只暴露单个会话和受限 part 能力;生成型对象可边产生边分片,但必须持久化 part 清单。会话 TTL 由最大上传耗时和恢复窗口决定,清理宽限与业务保留分开,不使用一个固定天数覆盖全部租户。
发布门禁要证明生命周期闭环
发布时必须守住“每个上传会话只有一份权威 part 清单,完成、未知与清理竞态最终都能对账收敛”。验收同时覆盖正常、慢、断连、重复、结果未知、权限撤销和清理恢复;核对接收字节、最终对象、业务引用、临时对象和删除结果。任何临时放宽都绑定 owner、影响前缀、补偿扫描和到期条件,过期自动阻断。
