OSS 对象存储

基础上传阶段,业务服务既接收请求,又保存文件。这在早期很方便,但一旦文件量增长,业务服务器就会承担不该承担的职责:磁盘扩容、文件备份、下载带宽、跨机房同步。

对象存储的价值是把二进制文件从业务服务里拆出去。业务服务继续负责鉴权、元数据和业务规则,文件内容交给专门的存储系统。

客户端 -> 业务服务:申请上传凭证
客户端 -> 对象存储:直传文件或分片
对象存储 -> 回调/事件:通知业务服务
业务服务 -> 数据库:更新文件状态

为什么推荐直传

如果所有文件都先经过业务服务,再转发到对象存储,业务服务仍然要承担上传带宽和连接压力。更好的方式是客户端直传对象存储:

  1. 客户端向业务服务申请上传。
  2. 业务服务校验权限和配额,生成短期签名。
  3. 客户端使用签名把文件传到对象存储。
  4. 上传完成后,客户端提交完成请求,或由对象存储回调业务服务。

这样业务服务不再处理大流量文件内容,只处理控制面逻辑。

对象 key 设计

对象存储的 key 不要直接使用原始文件名。推荐包含业务、日期和随机 ID:

uploads/{tenant_id}/{yyyy}/{mm}/{dd}/{file_id}.{ext}

这样做有几个好处:

  • 避免同名文件覆盖。
  • 支持按租户和日期做清理、统计和迁移。
  • 不暴露用户上传的原始文件名。
  • 方便后续接入冷热分层和生命周期策略。

原始文件名应该只保存在元数据表里,用于展示和下载时的 Content-Disposition

元数据与一致性

对象存储只知道有一个对象,不知道它属于哪个用户、是否通过审核、能不能下载。因此数据库仍然是业务事实来源。常见状态如下:

created -> uploading -> uploaded -> scanning -> available
                                      -> rejected
                                      -> deleted

这里存在一致性问题:对象上传成功但数据库更新失败,或者数据库记录存在但对象不存在。解决思路不是强行做分布式事务,而是通过状态机和补偿任务修复:

  • uploading 状态超过一定时间未完成,清理临时对象。
  • uploaded 状态迟迟未进入扫描,重新投递扫描任务。
  • 定期比对数据库和对象存储,发现孤儿文件或悬挂记录。

生命周期

对象存储让容量扩展变简单,但成本也更隐蔽。系统需要从一开始就设计生命周期:

  • 临时分片 24 小时未完成自动删除。
  • 用户删除文件后先软删,再延迟物理删除。
  • 长期不访问的大文件转入低频或归档存储。
  • 审核拒绝的文件保留短期证据后清理。

对象存储解决了“文件放哪里”的问题,但用户访问文件时仍然可能慢。下一章会把下载链路迁到 CDN。