对象存储是半个互联网背后默默干活的主力:每一张上传的照片、视频、备份和静态资源,几乎都躺在像 Amazon S3、Google Cloud Storage 或 Azure Blob 这样的对象存储里。它就是我们 Google Drive 和 Pastebin 设计里用来卸载字节的那个持久、近乎无限的"桶"。它与数据库或文件系统的不同,在于一组刻意的约束——扁平命名空间、不可变对象、HTTP API——用便利换来近乎无限的扩展性和持久性。
- 是对象,不是文件或块——每个对象 = 一坨字节 + 一个 key + 元数据,存在扁平命名空间里(没有真正的目录),通过 HTTP 访问。
- 为持久性而生——靠跨可用区复制和纠删码做到约 11 个 9;把"绝不丢这个数据"交给存储层。
- 近乎无限 + 便宜——可扩展到 EB 级,无需容量规划;按存储量和请求数付费。
- 对象不可变——你替换整个对象,而不是原地编辑;没有便宜的"重命名"或"追加"。
- 大文件用分段上传(multipart);客户端通过预签名 URL 直传,字节不经过你的应用服务器。
- 存储分级 / 生命周期把数据从热到归档逐级下沉以省钱;搭配 CDN 做全球快速读取。
对象存储把不可变的字节块按 key 存在扁平命名空间里,通过 HTTP API 暴露。它用纠删码加跨可用区复制做到约 11 个 9 的持久性,无需预置就能扩到 EB 级,而且便宜。大文件用分段上传,字节通过预签名 URL 直传。它不是文件系统(无重命名、无原地编辑、列举要分页且不免费),也不是数据库(不能查询)——它就是你往里放字节、再用 CDN 兜在前面的那个持久的桶。
对象 vs 块 vs 文件存储
存储有三种范式,选对象存储意味着你是有意接受它的模型:
| 方面 | 对象(S3) | 块(EBS) | 文件(NFS) |
|---|---|---|---|
| 基本单位 | 对象(字节 + 元数据) | 定长的块 | 目录里的文件 |
| 访问方式 | 按 key 走 HTTP API | 挂载为磁盘 | 挂载、POSIX 路径 |
| 修改 | 替换整个对象 | 可改任意块 | 原地编辑 |
| 扩展 | 近乎无限 | 单卷有上限 | 受服务器限制 |
| 最适合 | 字节块:媒体、备份、资源 | 数据库、操作系统盘 | 共享的应用文件 |
块存储是裸磁盘(适合数据库的文件);文件存储是可挂载的共享层级(适合老应用)。对象存储放弃了原地编辑和真正的目录树,换来无限扩展和简单的网络 API——非常适合写一次、读多次的字节块。
对象的构成与扁平命名空间
一个对象是三样东西:数据(字节)、唯一的 key(它在桶内的名字)和元数据(内容类型、大小、自定义标签)。关键在于命名空间是扁平的——没有真正的文件夹。像 2023/09/photo.jpg 这样的 key 看着是层级,但斜杠只是 key 里的字符;"文件夹"是按公共前缀列举 key 时由界面制造出来的便利。正是这种扁平让系统能扩展:没有目录树要遍历或加锁,只有一张巨大的分布式 key→字节块的映射。
HTTP API
你用普通的 HTTP 动词对一个 key 操作:
PUT /my-bucket/2023/09/photo.jpg # 上传(已存在则替换)
GET /my-bucket/2023/09/photo.jpg # 下载
DELETE /my-bucket/2023/09/photo.jpg # 删除
GET /my-bucket?prefix=2023/09/ # 按前缀列举 key(分页)
# 大文件:分段上传(并行、可断点续传)
initiate → upload part 1..N (并行) → complete
两个模式在大规模下很关键。分段上传把大对象切成多个分片并行上传、服务端再拼起来——对几个 GB 的大文件可断点续传、又快。预签名 URL 是有时限的签名链接,让客户端直接对存储 PUT/GET 某对象,而不必让字节经过你的应用服务器——这对把大流量挡在后端之外至关重要(正是 Drive 设计搬运分块的方式)。
靠复制和纠删码做持久性
招牌特性就是持久性——厂商宣称约 11 个 9(99.999999999%),意思是丢一个对象的概率小到可以忽略。靠两个技术达成。复制把副本存到多个可用区(物理隔离的数据中心),所以一个机房着火或被淹不会丢数据。纠删码比整份复制更聪明也更省:它把对象切成 k 个数据分片加 m 个校验分片,只要拿到 k+m 中任意 k 个就能重建对象。比如 10 数据 + 4 校验,可容忍丢任意 4 个分片,而存储开销只有 1.4 倍,而非三副本的 3 倍。
对象 → 切成 k=10 数据 + m=4 校验 = 14 个分片
分散到 14 块盘 / 可用区
从任意 10/14 重建 → 可容忍丢最多 4 个
存储开销 = 14/10 = 1.4 倍 (对比三副本的 3 倍)
一致性模型
历史上 S3 的某些操作是最终一致的——刚写入的对象在读时可能短暂 404。现代对象存储现在提供强一致的写后读(read-after-write):一旦 PUT 成功,随后的 GET 就返回新数据。这对"写完立刻读"的流水线很重要。不过列举(listing)可能仍略有延迟,而且没有跨对象事务——每个对象操作各自独立、各自原子。
存储分级与生命周期
不是所有数据访问频率都一样,所以对象存储提供不同价格/延迟点的存储类别,以及自动在其间迁移的生命周期规则:
| 类别 | 用途 | 取舍 |
|---|---|---|
| 标准(热) | 频繁访问 | 存储最贵,即时访问 |
| 低频访问 | 每月访问 | 存储更便宜,取回收费 |
| 归档(冷) | 备份、合规 | 非常便宜,恢复要数分钟到数小时 |
一条生命周期策略可能让对象在标准类待 30 天,转到低频访问,一年后再转归档,最后过期删除——全自动,大幅降低老数据的成本。
扩展与性能
因为命名空间是扁平的,存储把 key 空间分区,几乎线性扩展——没有目录树成为瓶颈。历史上性能与 key 的前缀相关(存储按前缀分区,所以很多 key 共享一个前缀会形成热点),这也是过去建议把前缀随机化的原因;现代 S3 会自动扩展前缀,但在极端吞吐下把负载分散到多个 key 仍有帮助。对读多的公开内容,你在前面放一个 CDN,大多数读从边缘返回、根本不打到源桶。
用来干什么
- 静态资源与媒体——图片、视频、JS/CSS 包,通过 CDN 分发。
- 备份与归档——持久、便宜、按生命周期分级的冷存储。
- 数据湖——给批处理/分析作业直接读取的原始数据(读时模式 schema-on-read)。
- 应用字节块——用户上传、文件同步分块、生成的产物(Drive、Pastebin)。
常见坑
- 没有重命名/移动——"重命名"=复制到新 key + 删旧 key;移动一个大"文件夹"是很多次操作。
- 列举不免费——大桶按分页批次列举、每次请求都计费;别把列举当查询引擎用。
- 不是数据库——不能查询、连接或部分更新;要配一个元数据数据库(就像 Drive 设计那样)。
- 请求成本——上百万个小对象,请求费可能比存储费还高;把小对象批量打包。
对象存储用文件系统的便利(重命名、原地编辑、廉价列举)和数据库的能力(查询)换来三个超能力:靠扁平命名空间无限扩展、靠纠删码加跨可用区复制做到约 11 个 9 的持久性、以及极简的 HTTP API。用它存不可变的字节块,用预签名 URL + 分段上传直接搬运字节,用生命周期规则分级,再用 CDN 兜在前面。
对象 vs 块 vs 文件?对象 = 按 key 走 HTTP 的字节块、扁平命名空间、无限(S3);块 = 给数据库用的裸磁盘(EBS);文件 = 可挂载的 POSIX 层级(NFS)。
11 个 9 怎么来的?跨可用区复制加纠删码(k 数据 + m 校验,任意 k 个即可重建)——比三副本便宜得多的持久性。
为什么用预签名 URL 和分段上传?客户端直接对存储上传/下载(字节不碰你的服务器);分段把大上传并行化并支持断点续传。
它是文件系统吗?不是——扁平命名空间、不可变对象、没有便宜的重命名、列举要分页且不免费。"文件夹"只是 key 前缀。
为什么前面要放 CDN?读取是批量且可缓存的;边缘服务热门内容,源桶不被打爆。