网站上传的图片,到底该存在哪里?我在实习期间第一次听说“OSS 对象存储”,就是从这个问题开始弄懂它的。
比如给网站加一个上传头像的功能,图片存进应用服务器的 uploads/ 目录当然能用,但部署到第二台服务器后,两台机器未必能读到同一张图;如果目录在容器的临时文件系统里,容器重建还可能把它一起带走。
对象存储让应用把文件交给一个独立的存储服务,再通过文件的标识去上传、读取和删除。应用服务器可以换,图片仍留在原来的存储空间里。对于第一次接触云存储的 Web 开发者,头像上传是理解这套分工的一个好例子。
OSS 是什么?先分清产品和存储方式
在阿里云的语境里,OSS 是 Object Storage Service,对象存储服务的产品名。对象存储(Object Storage)则是一类存储方式,Amazon S3 也是这类服务。它们的基本概念相近,接入时仍要查看各自的 API、签名规则和功能限制。
对象存储中的“对象”可以是一张 JPEG、一份 PDF、一段视频,也可以是 JSON 或压缩包。这里的对象不要求是程序里的类实例,更不要求把文件转换成 JSON。上传图片时,存进去的主体仍然是图片的字节。
应用面对的是按名称存取数据的接口,存储服务负责管理底层存储。通常通过 HTTP API 操作,也可以用 SDK、命令行工具或控制台。阿里云 OSS 概述介绍了这些入口。
Bucket、Object 和 Key 怎样定位一张图片
先看几个经常一起出现的词:
| 名称 | 含义 | 头像示例 |
|---|---|---|
| Bucket,存储空间 | 容纳对象的容器,配置地域、访问规则等 | avatar-demo-bucket |
| Object,对象 | 文件内容及相关元数据 | 一张 JPEG 的字节和文件属性 |
| Key,对象键 | 对象在 Bucket 内的完整名称 | users/42/avatars/7f3a.jpg |
| Region,地域 | Bucket 所在的数据中心地域 | 杭州 cn-hangzhou |
| Endpoint,访问域名 | 客户端向该地域的 OSS 服务发请求的地址 | oss-cn-hangzhou.aliyuncs.com |
这里的 Bucket 名和文件名都是教学示例。可以用下面这份记录理解对象的组成,它不是 OSS API 的实际返回格式:
{
"bucket": "avatar-demo-bucket",
"key": "users/42/avatars/7f3a.jpg",
"contentType": "image/jpeg",
"sizeBytes": 183240,
"metadata": {
"purpose": "avatar"
}
}图片的二进制内容没有放进这份 JSON。Content-Type、大小以及自定义属性描述图片,Key 用来找到图片。purpose=avatar 只是说明信息,不会让 OSS 自动判断谁有权修改头像。
在一个 Bucket 内,Key 标识一个对象;开启版本控制后,同一个 Key 可以保留多个版本,还需要 Version ID 才能指定某个历史版本。相关定义见 Object 概述。
通过 OSS 默认域名访问时,地址通常由 Bucket、地域 Endpoint 和 Key 组合而成,也可以绑定自定义域名。URL 能定位对象,并不表示任何人都能读取它,私有对象仍需要通过权限校验。
Key 里的斜杠,为什么看起来像文件夹
users/42/avatars/7f3a.jpg 很像一个目录路径。对于这里讨论的普通 OSS Bucket,它首先是一个完整的字符串名称。
控制台按 / 把名称显示成文件夹,列举接口也可以按 users/42/ 这样的前缀筛选对象。上传前无须逐层创建 users、42 和 avatars 目录。Bucket 文档明确说明了这种扁平结构。
这会影响程序的行为。比如把 users/42/ 改成 members/42/,对普通对象通常需要逐个复制到新 Key,再删除旧对象。不能把它当成本地文件系统的一次目录重命名,更不能假设整个“目录”会原子地一起移动。
修改内容也有区别。普通 PutObject 按整个对象写入,同 Key 写入会替换当前内容,是否保留旧版本取决于版本控制设置。设计业务时,最好把更新理解成“上传一个新对象,再切换引用”。
某些接口有专门的能力,例如 OSS 的 AppendObject可以向 Appendable 对象末尾追加数据。它有对象类型和使用限制,不能由此推导出对象存储支持任意位置写入、文件锁等完整的文件系统语义。
对象存储、文件存储、块存储怎么选
这几种方式的差别,主要在应用怎样访问数据。
| 类型 | 应用看到什么 | 常见使用场景 |
|---|---|---|
| 对象存储 | Bucket、Key,以及上传、下载等 API | 图片、视频、备份文件、数据集 |
| 文件存储 | 目录和文件路径,常通过 NFS、SMB 访问 | 多台机器共享目录、依赖文件接口的现有应用 |
| 块存储 | 可按块读写的磁盘卷,通常由操作系统建立文件系统 | 云服务器系统盘、数据库数据盘 |
这张表比较的是接口层,不是在说底层用了不同种类的硬盘。文件系统本身也可以建立在块存储上。AWS 的存储类型对比提供了更完整的选型背景。
头像通常是上传一整张、读取一整张,很适合对象存储。数据库运行时会频繁更新数据页,并依赖特定的持久化和并发语义,不能把普通对象存储挂载工具提供的目录直接当成 PostgreSQL 数据盘。数据库的备份文件则很适合放进去。
如果应用只有一台服务器、文件很少,而且已经正确配置持久卷和备份,本地目录也可以满足需求。对象存储减少了共享文件和扩容方面的工作,同时引入网络请求、云服务权限和独立账单。没有必要为了“上云”多接一项服务。
数据库保存关系,OSS 保存文件内容
以 PostgreSQL 为例,用户表负责回答“42 号用户现在使用哪张头像”。OSS 负责按 Key 返回图片内容。应用可以保存这样的业务记录,同样只是示意数据:
{
"userId": 42,
"avatarBucket": "avatar-demo-bucket",
"avatarKey": "users/42/avatars/7f3a.jpg",
"avatarStatus": "ready"
}我会优先保存稳定的 Bucket 和 Key,或者保存能映射到它们的文件 ID。临时下载 URL 会过期,CDN 域名也可能变化,把这些 URL 当成文件身份,后面更换访问方式时就得修改业务数据。
文件归属、审核状态、哪些用户有权查看,仍由业务数据库和应用逻辑管理。OSS 的自定义元数据可以描述文件,但不会自动提供关系查询和跨系统事务。
数据库也能保存二进制数据,小文件在某些场景下这样处理很方便。选择单独的对象存储,是为了让文件传输和容量增长不都压在数据库上;代价是文件和业务记录之间的一致性需要自己处理。
浏览器直传 OSS,一次上传经过哪些步骤
一种做法是浏览器先把图片发给后端,再由后端上传 OSS。后端容易集中检查内容,但也要承担文件中转的带宽和连接。
另一种做法是浏览器先向后端申请临时上传权限,再把文件直接传给 OSS。阿里云支持预签名 URL、带策略的表单上传和 STS 临时凭证等方式。这里用预签名 URL 解释,具体机制见浏览器上传授权文档。
下面是一个头像服务的示例设计。头像 API 是应用自己的接口,PostgreSQL 保存上传任务和用户记录。
- 用户登录后,浏览器申请上传。后端检查身份、上传次数和额度,为本次上传生成唯一的暂存 Key,并在数据库中建立
pending记录。用户 ID 取自登录身份,不直接信任浏览器传来的值。 - 后端签发短期有效的上传 URL,限定本次操作和目标 Key。浏览器只拿到这次上传的授权,不需要持有服务端的长期凭证。
- 浏览器按约定发送
PUT,文件字节直接到 OSS。参与签名的请求头要与实际请求一致,例如Content-Type。 - 浏览器通知后端“传完了”。后端按上传任务查找对象,核实实际大小,并读取、解码图片验证内容。只有元数据里的
image/jpeg,还不足以证明它是一张合法 JPEG。 - 后端把通过验证的图片内容写到一个新的正式 Key,再在数据库事务中更新用户的头像引用和上传状态。这个正式 Key 不向浏览器授予写权限。客户端重试完成请求时,后端应返回同一任务的结果。
第 5 步看起来多了一次写入,却解决了一个容易遗漏的问题:上传 URL 在过期前可以重复使用,同一个暂存对象可能再次被覆盖。预签名上传文档明确说明了这一点。
因此,这个例子写入正式 Key 的是后端实际验证过的那份字节。不能先验证暂存对象,再无条件复制当时最新的暂存内容,否则两步之间仍可能发生覆盖。大文件系统可以利用具体版本或条件请求设计更高效的方案,但也需要保持“验证的内容”和“最终引用的内容”一致。
这个分工适合减少大文件中转。如果要求每个上传字节都先通过后端检查,或者流量很小,中转上传可能更简单。浏览器直传也不会让后续审核、转码的计算成本消失。
临时授权和 CORS 分别解决什么问题
预签名 URL 可以理解为对某次请求的临时授权。谁拿到它,通常就能在有效条件下执行相应操作,因此不要把私有文件的签名 URL 写进公开页面或长期保存为下载地址。临时链接也不天然是“一次性链接”。
CORS 管的是浏览器跨来源请求和响应读取。如果页面与 OSS Endpoint 不同源,需要在 Bucket 上配置允许的页面来源、方法和请求头。CORS 配好之后,请求仍要通过 OSS 的签名和权限校验;它不能代替用户登录。
文件大小也要区分“前端提示”和“服务端强制限制”。只在页面 JavaScript 中判断 5 MB,无法阻止调用者自己构造请求。需要在上传入口直接限制大小时,可以评估 OSS 表单上传策略中的 content-length-range,不要假设任何预签名 PUT URL 都自带大小上限。具体条件见 PostObject 文档。
私有文件下载也遵循相似的分工:应用先判断当前用户是否有权查看,再签发短期下载 URL。公开站点图片可以使用公开读取或合适的 CDN 访问方案,写入权限仍应单独控制。
OSS 上传成功,不等于业务已经完成
OSS 的单对象操作提供原子性和强一致性。按照官方一致性说明,收到成功的 PUT 响应后,可以直接从 OSS 读到完整的新对象。不能笼统地说“对象存储都只能最终一致”。
这项保证不覆盖 PostgreSQL 与 OSS 之间的操作。正式图片写入成功后,数据库更新仍可能失败;浏览器也可能在直传成功后就关闭页面,没有通知后端。
因此,示例中的 pending 和 ready 有实际用途。应用只引用完成校验的文件,完成接口支持重试,后台任务按上传记录清理超过保留时间的暂存文件。正式对象写入后数据库失败,也要能重试关联或清理孤立对象。删除旧头像前,还要确认它不再被业务引用,并为进行中的请求留出余地。
大文件还会用到分片上传:先初始化上传任务,再分别发送分片,最后调用完成接口合并为对象。失败时可以重传相关分片。未完成的分片需要主动取消或通过规则清理,否则仍可能产生存储费用,见 OSS 分片上传文档。
OSS 和 CDN 为什么经常一起出现
OSS 保存源文件。CDN 把文件缓存到分布式节点,用户请求命中缓存时,由缓存节点返回内容;未命中时再向 OSS 回源。OSS 与 CDN 的工作流程描述了这层关系。
所以,OSS 已经更新成功,用户仍可能看到 CDN 或浏览器缓存中的旧头像。存储层的强一致性不会自动刷新每一层缓存。
头像更新可以使用新的 Key,例如把 7f3a.jpg 换成 9bd2.jpg,数据库随后指向新地址。这样比反复覆盖 avatar.jpg 更容易管理缓存。若必须复用 URL,就要配置缓存有效期和刷新策略。对于私有内容,还需专门设计 CDN 的访问控制与缓存规则,不能只检查源站是否私有。
对象存储账单为什么不能只看容量
常见计费项包括存储容量与时长、请求次数和数据传出。低频或归档类型还可能涉及取回费用、最低存储时长,以及恢复等待时间。具体组合取决于存储类型和功能,选型时应查看 OSS 计费说明和存储类型说明。
可以先算自己的使用量。假设每张图是十进制的 200 KB,每天完整下载 100,000 次,那么传输量约为 20 GB/天。这只是用量演算,没有包含协议开销、缓存命中或图片处理,也不是云厂商报价。
频繁展示的头像适合优先考虑标准存储。很少访问的历史备份,可以根据恢复时间要求考虑更冷的存储类型。生命周期规则能按条件自动转存或删除对象,配置方法见生命周期文档。
还要区分持久性和可用性:前者关心数据是否丢失,后者关心服务在需要时能否访问。存储服务的冗余设计不会替你避免应用误删。是否开启版本控制、怎样保留备份、怎样恢复,仍要根据数据用途决定。
关于 OSS 对象存储的几个常见问题
OSS 和网盘有什么区别?
网盘通常已经提供账号、目录、分享和同步等面向人的功能。OSS 主要提供给程序使用的存储接口。它可以成为网盘的底层组件,完整的网盘产品还需要业务层。
能把 OSS 挂载成普通磁盘吗?
可以通过适配工具呈现为文件路径,但底层仍受对象接口和工具实现约束。已有程序如果依赖文件锁、随机写入或多客户端一致性,需要逐项验证,不能只因路径能打开就认为它与本地磁盘等价。
对象存储只能放图片、视频这些非结构化数据吗?
不是。CSV、JSON、Parquet 也可以作为对象保存。文件内部可以有结构,但对象接口本身不会因此变成能执行 JOIN 和事务的关系数据库。查询通常交给应用或专门的计算引擎。
上传成功后,直接把文件 URL 存进数据库可以吗?
稳定的公开 URL 在简单系统里可以使用。需要私有访问、切换域名或更换分发方式时,保存文件 ID 或 Bucket、Key 更容易维护。会过期的预签名 URL 不适合作为长期文件标识。
用一个小实验把这些概念连起来
如果已经有 OSS 测试环境,可以按创建 Bucket 文档准备一个私有测试 Bucket,再完成下面几步:
- 上传一张不含个人信息的测试图片,Key 设为
users/42/avatars/demo.jpg,观察控制台怎样显示“文件夹”。 - 对比直接匿名访问与通过控制台或 SDK 授权访问的结果,区分“知道地址”和“有权读取”。
- 用新 Key 上传另一张图,在自己的测试记录中切换引用,观察旧对象仍然存在。
- 为一个测试对象生成短期签名链接,观察有效期前后的访问结果,最后删除测试资源。
接入自己的头像功能时,先把一条记录走通:谁申请上传、OSS 中的 Key 是什么、哪一步确认文件可用。随后测试“上传成功但数据库失败”和“完成请求重复发送”这两种情况,它们会直接检验你是否把文件存储和业务状态衔接好了。