这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
OSS 对象存储
基础上传阶段,业务服务既接收请求,又保存文件。这在早期很方便,但一旦文件量增长,业务服务器就会承担不该承担的职责:磁盘扩容、文件备份、下载带宽、跨机房同步。
对象存储的价值是把二进制文件从业务服务里拆出去。业务服务继续负责鉴权、元数据和业务规则,文件内容交给专门的存储系统。
客户端 -> 业务服务:申请上传凭证
客户端 -> 对象存储:直传文件或分片
对象存储 -> 回调/事件:通知业务服务
业务服务 -> 数据库:更新文件状态
为什么推荐直传
如果所有文件都先经过业务服务,再转发到对象存储,业务服务仍然要承担上传带宽和连接压力。更好的方式是客户端直传对象存储:
- 客户端向业务服务申请上传。
- 业务服务校验权限和配额,生成短期签名。
- 客户端使用签名把文件传到对象存储。
- 上传完成后,客户端提交完成请求,或由对象存储回调业务服务。
这样业务服务不再处理大流量文件内容,只处理控制面逻辑。
对象 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。
