对象存储
对象存储以带有标识和元数据的对象保存内容,应用通过对象键读取、写入或删除它。图片、视频、附件和备份可以放在对象存储中,数据库则保存文件归属、业务状态和对象位置等信息。
理解对象存储,重点是把对象键、内容、版本和访问权限分开。一个看起来像路径的键,不等于本地文件系统的目录;成功上传一个对象,也不等于关联的数据库记录已经一起提交。
1. 面试核心
先记住这几条,再读正文- 对象由键、内容和元数据组织,通常放在桶等命名空间中。常见接口通过 HTTP API 访问;文件存储以路径和文件操作为中心,块存储向上层提供可寻址的块。卡 · 综合题
- 对象键决定怎样定位内容,原文件名、业务记录 ID 和内容摘要不必是同一个值。按摘要去重还要处理校验、并发占用、权限与引用关系。卡 · 对象键与内容组织
HEAD读取元信息,GET读取内容。ETag 不是通用文件 MD5,Content-Type 也不是内容真实性证明。卡 · 读写接口与元数据- S3 多段上传经历创建上传、上传各段、完成合并。各段成功不等于最终对象已经完成,同编号重传、完成清单和中止清理都需要按服务接口处理。卡 · 多段上传
- 开启版本管理后,同一键可以保留多个版本;S3 普通删除可以写入删除标记。当前读不到不等于所有历史字节已被删除。卡 · 版本与删除
- 一致性和覆盖规则属于具体服务、接口与操作范围的契约。强一致读不能阻止无条件覆盖,也不能把对象与业务数据库变成一个事务。卡 · 一致性与覆盖条件
- 私有对象需要按主体、动作和资源授权。预签名 URL 是有限范围的访问凭据,不天然是一次性链接,持有者在有效条件内可能重复使用。卡 · 访问权限与预签名 URL
2. 对象、文件与块
对象是一段内容及其关联元数据,应用通过键标识它。桶(bucket)是组织对象的命名空间和管理边界,常承载访问策略、生命周期等配置;具体能力取决于服务。
以视频附件为例,可以把视频字节存为对象,把标题、上传者、审核状态和对象键存入业务数据库。这样,多台应用服务器可以访问同一份内容,不必假定文件就在当前实例的本地磁盘上。
| 存储模型 | 应用主要面对什么 | 适合讨论的能力与边界 |
|---|---|---|
| 块存储 | 块地址与读写范围 | 上层可建立文件系统或数据库;对象命名、目录和业务权限通常需要上层实现 |
| 文件存储 | 文件路径、目录、文件句柄 | 文件系统提供相应的读写、目录及重命名语义;本地磁盘和网络文件系统的访问方式仍有区别 |
| 对象存储 | 桶、对象键、内容及元数据 | 通过对象 API 管理内容;不能默认拥有 POSIX 文件锁、目录事务或任意原地修改语义 |
常见 S3 风格接口用 HTTP 请求传递对象和控制信息,SDK 帮助处理签名、重试与错误解析。MinIO 提供 S3 兼容接口,但“S3 兼容”不表示所有服务的功能、限额和故障行为都完全相同。
对象存储不必部署在公有云,文件存储也不必部署在本机。区分它们要看提供的接口与语义,不能只看服务器在哪里。
3. 对象键与内容组织
3.1 键不等于目录路径
对象键是对象在所属命名空间中的标识。例如 users/42/report.txt 可以是一个完整键,其中的 / 可用于前缀分组。以 Amazon S3 通用桶为例,这种前缀不是嵌套的文件系统目录,不能据此假定重命名一个前缀就能原子移动整棵目录树。
键和访问 URL 也不同。URL 还包含端点、桶的寻址方式、编码和可能的签名参数;复制了 URL,不代表取得了独立于权限和有效期的永久访问能力。
设计键时,应明确几种身份各自的用途:
| 标识 | 用途 |
|---|---|
原文件名 report.txt | 展示或下载时提供名称;多个用户可以上传同名文件 |
业务 ID doc-7 | 标识一份业务附件及其权限、状态 |
对象键 users/42/doc-7/content | 在存储中定位内容 |
| 内容摘要 | 按明确算法和字节范围校验或识别内容 |
| 版本 ID | 在支持版本管理的服务中定位同一键的特定版本 |
不要把客户端文件名直接当作可覆盖任何对象的授权依据。即使采用随机键或租户前缀,也仍然要检查谁能操作这个键,具体授权原则见认证与授权。
3.2 按摘要去重还需要什么
内容寻址是把内容摘要用于内容标识。比如对完整文件计算 SHA-256,再按摘要组织对象键,可以让相同字节指向同一份存储内容;但摘要算法、字节范围与可信校验必须一致。
客户端提交一个摘要,只是提出声明,服务端不能因此跳过内容或权限验证。并发上传相同内容时,也不能只做“先查不存在,再写入”,还需要条件写入或权威元数据中的唯一约束。通用的并发占用机制见幂等键。
存储去重也不等于业务去重。两个用户可以各自拥有一份附件记录,但共同引用同一份底层字节;删除其中一条记录时,不能把另一个用户仍在使用的内容直接删掉。共享内容还要避免通过“是否已经存在”泄露其他用户是否保存了某个文件。
4. 读写接口与元数据
常见对象 API 把内容操作和元信息查询分开:
| 操作 | 作用 | 需要注意 |
|---|---|---|
PutObject | 写入指定键的对象 | 同键写入是否覆盖、产生版本或被条件拒绝,要看请求与配置 |
GetObject | 读取对象内容 | 返回的数据仍要正确读取和关闭;范围读取不等于范围写入 |
HeadObject | 查询大小、类型、ETag 等元信息 | 不返回完整内容,也不自动证明调用方拥有业务权限 |
ListObjects | 按前缀等条件列举对象 | 通常分页;一次返回不能默认代表完整桶内容 |
DeleteObject | 删除当前对象或指定版本 | 开启版本管理时需要区分删除标记与历史版本 |
元数据是描述对象的附加信息。它可以包含服务维护的长度、时间,也可以包含上传时指定的 Content-Type、自定义文档 ID 等。以 S3 为例,自定义元数据不是可以随意更新的数据库行字段,修改时需要按其复制、替换等接口语义处理。
下面使用 boto3 1.40.0,连接已经准备好的本地隔离服务;lab-objects 是实验桶,凭据由实验环境注入。代码只演示写入、查看元数据和读取内容:
import os
from contextlib import closing
import boto3
from botocore.config import Config
s3 = boto3.client(
"s3",
endpoint_url="http://127.0.0.1:9000",
aws_access_key_id=os.environ["OBJECT_ACCESS_KEY"],
aws_secret_access_key=os.environ["OBJECT_SECRET_KEY"],
region_name="us-east-1",
config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
)
bucket = "lab-objects"
key = "users/42/report.txt"
s3.put_object(
Bucket=bucket, Key=key, Body=b"hello\n",
ContentType="text/plain", Metadata={"document-id": "doc-7"},
)
metadata = s3.head_object(Bucket=bucket, Key=key)
response = s3.get_object(Bucket=bucket, Key=key)
with closing(response["Body"]) as body:
content = body.read()
print(metadata["ContentLength"], metadata["Metadata"]["document-id"])
print(content)
输出为 6 doc-7 和 b'hello\n'。document-id 随对象保存,但它没有因此变成业务数据库中的授权记录;Content-Type 也是上传者提供的信息,不能单凭它证明实际字节一定是对应格式。
ETag 是服务返回的实体标识,可按接口约定用于条件请求。它不应被一律解释为完整文件的 MD5:多段上传、加密和不同实现都可能改变其含义。需要端到端内容校验时,应明确使用的校验算法及覆盖范围,并检查服务是否验证该校验值。
5. 多段上传
多段上传(multipart upload)把一个对象拆成若干段,独立传输后再提交成最终对象。它适合减少大对象传输失败时的重传范围,也允许按服务约束并行上传。
这里的多段上传是一组对象 API 操作,不等于在一次 HTTP 请求里使用 multipart/form-data。以 S3 的通用桶接口为例:
| 阶段 | 关键状态与动作 |
|---|---|
| 创建 | CreateMultipartUpload 返回 UploadId;记录目标键和对象元数据 |
| 上传各段 | UploadPart 携带上传 ID、段号及内容;保存返回的 ETag 或相应校验信息 |
| 查询进度 | ListParts 查询服务端已保存的段;客户端本地进度不是唯一事实依据 |
| 完成 | CompleteMultipartUpload 提交按段号排序的清单,引用对应段的 ETag |
| 放弃 | AbortMultipartUpload 中止会话,并按服务规则确认未完成段的清理 |
S3 允许各段以不同顺序到达,最终按完成清单中的段号升序组合。同一上传 ID 下再次上传相同段号,会替换该段,所以重试要确认提交的是同一份内容;不同上传 ID 的段不能直接混用。
完成清单还必须与应用预期相符。存储服务不会替应用猜测“本来应该有三段,但客户端只提交了两段”。业务需要核对总大小、预期段集合和整体校验,不能只把各段成功数相加。
以 S3 通用桶的普通多段上传为例,除最后一段外,各段至少为 5 MiB;这是具体服务限制,不是所有对象存储都通用的数字。各段都收到成功响应后,仍需要完成操作。原键已有对象时,未完成的新上传也不能直接当作已经替换旧对象。
完成响应同样要按 API 解析。S3 的 CompleteMultipartUpload 可能在开始处理时先发出 HTTP 200,之后在响应体中返回错误;不能只看状态行就把业务记录标记为完成。SDK 通常处理这种协议细节,调用方仍需检查最终结果。
中止也不能代替对进行中请求的管理。停止并等待并发上传后,再按服务约定中止或检查残留,可以避免把仍在飞行中的请求误判为已经全部清理。完成上传与业务状态提交之间的失败处理属于应用协议,不能交给桶的生命周期规则自动解决。
6. 版本与删除
对象版本管理让同一键保留多个内容版本,但是否支持、是否开启以及怎样计费,取决于服务和桶配置。
以开启版本管理的 S3 通用桶为例,再次写入同一键会产生新的版本 ID,旧版本仍可按版本 ID 读取。未指定版本的普通删除通常创建删除标记,让当前读取表现为对象不存在,而不是立刻销毁所有历史内容。
因此,需要分别确认:当前版本是否可见、旧版本是否保留、谁可以读取旧版本,以及是否执行了指定版本删除。版本管理可以帮助恢复误覆盖,但保留历史版本不等于已经完成独立备份,也不替应用解决“当前应该引用哪个版本”的业务判断。
如果业务要求一个附件记录长期指向同一份内容,可以保存不可变对象键,或在服务支持时记录准确的版本 ID。只保存一个会被覆盖的键,后续读取可能得到另一份内容。
7. 一致性与覆盖条件
7.1 先确认具体服务的承诺
对象存储不统一等于“最终一致”或“强一致”。需要说明服务、桶类型、接口、操作以及是否经过缓存:
- Amazon S3 对象 API 对成功写入后的读取和列举提供强一致性,单个键的更新具有原子性;不能把它扩大成跨对象或跨数据库事务。
- Cloudflare R2 对直接对象访问的读写、删除、元数据及列举提供强一致性;经启用缓存的自定义域名访问时,可能继续读到旧内容或缓存的不存在响应。权限配置的传播也有另外的规则。
这些是各自的服务契约,不是从本地 MinIO 的一次读写实验推出来的结论。跨区域复制、CDN、应用缓存和权限变更也不能仅凭“对象存储强一致”几个字合并判断。
**读到不存在,只能说明那次读取观察到的状态。**如果一个结果未知的上传仍在处理中,它可能在这次读取之后才提交。强一致读取不会自动等待或取消此前的所有请求,失败恢复仍需要识别原操作的状态。
7.2 防止覆盖要使用写入条件
读写一致性解决的是观察到什么,写入条件解决的是在什么前提下允许修改。两个客户端都执行无条件写入时,即使服务强一致,也可能覆盖彼此的结果。
以 S3 支持的条件写入为例,If-None-Match: * 要求当前不存在相应对象;If-Match 要求当前 ETag 与预期一致。条件必须由服务在写入操作中判断,不能拆成客户端先 HEAD、再无条件 PUT 两步。
条件失败时需要重新读取并作业务判断,不能直接去掉条件强行覆盖。ETag 比较也不是通用的业务版本比较:内容相同但元数据变化,或业务状态变化,都不应假定必然得到不同 ETag。
即使条件写入成功,数据库中的附件记录仍然是另一项操作。需要为对象与业务记录设计状态推进、幂等和核对机制,通用崩溃窗口见幂等键。
8. 访问权限与预签名 URL
私有桶、公开读取、服务账号权限和业务用户授权是不同层次。应用服务有权读取桶,不代表任何登录用户都有权下载其中任意对象。服务端仍要核对对象归属和操作,再决定是否代为读取或授予临时访问。
预签名 URL 是把特定请求的签名与有效条件放入 URL,让持有者无需拿到长期密钥也能执行该请求。以 S3 为例,它会约束目标对象、HTTP 方法及有效期,还受到签名者权限和凭据有效性的限制。
链接通常可以在有效条件内重复使用,不能默认“一次下载后自动失效”。如果业务要求一次性访问,需要额外的服务端状态与消费机制。生成链接前也必须先完成业务授权,不能允许客户端随意指定其他租户的键来请求签名。
给浏览器或移动端提供访问时,应限制动作与对象范围,而不是把服务长期密钥交给客户端。CORS 规定浏览器中的跨域请求和响应访问规则,不能替代对象权限检查。
9 · 面试题卡片
本页题目与参考回答
9.1 项目中如何使用对象存储,它与本地文件存储有什么区别?
题型:综合题
考察重点:内容与业务元数据的分工、对象接口和文件接口、项目中的访问路径。
相关内容:对象、文件与块、读写接口与元数据、访问权限与预签名 URL。
参考回答
**对象存储通过键和对象 API 管理内容,本地文件存储通过当前机器上的文件路径和文件系统接口访问。**例如视频内容放入对象存储,数据库保存上传者、状态与对象位置,多台应用服务器可以共享访问。
介绍项目时还要说清实际服务、使用的 HTTP API 或 SDK、键怎样生成,以及谁有权限读写。MinIO 的 S3 兼容接口不等同于本地文件目录,也不能只因设成公开访问就认为授权问题已经解决。
9.2 对象键怎样设计,用内容哈希能解决重复文件的全部问题吗?
考察重点:原文件名、业务身份和内容身份的区别,并发去重、引用与权限。
相关内容:对象键与内容组织。
参考回答
**键要服务于定位与隔离,内容哈希只能帮助识别内容,不能解决全部业务去重问题。**原文件名用于展示,业务 ID 标识附件记录,对象键定位存储,三者可以分开。
使用摘要去重时,要明确算法并核验实际字节,还要处理并发占用。多个业务记录引用同一份内容后,需要分别维护权限与引用,删除一条记录不能误删仍被使用的内容,也不能通过存在性检查泄露其他用户的数据。
9.3 HEAD、Content-Type 与 ETag 各能说明什么,ETag 一定是文件 MD5 吗?
考察重点:元数据查询、内容声明、实体标识和完整性校验的区别。
相关内容:读写接口与元数据。
参考回答
**HEAD 查询元信息,Content-Type 描述声明的媒体类型,ETag 的含义由服务与操作定义,不一定是完整文件的 MD5。**多段上传和加密等情况尤其不能按单段明文对象的经验推断。
验证内容要明确校验算法和字节范围,并检查实际数据或服务验证结果;媒体类型也需要按业务要求核验,不能只相信上传头。
9.4 多段上传中,各段都成功后是否已经得到最终对象?
考察重点:上传会话、段身份、完成清单与中止的职责。
相关内容:多段上传。
参考回答
**还需要成功完成上传操作,上传段与提交最终对象是不同阶段。**S3 使用上传 ID 和段号标识各段,完成时提交有序清单及对应 ETag;同段号重传会替换该段。
应用要核对预期段集合、大小和整体校验,不能把不完整清单交给服务后期待它自动发现业务上的缺失。放弃时还要处理进行中的请求和残留段;完成结果要按 API 的完整响应判断。
9.5 开启版本管理后,删除一个键是否会删除所有历史内容?
考察重点:当前版本、历史版本、删除标记与业务引用。
相关内容:版本与删除。
参考回答
**不一定。开启版本管理的 S3 通用桶中,普通删除通常新增删除标记,旧版本仍可能保留并可按版本 ID 读取。**需要区分删除当前可见对象、删除指定版本与清理历史版本。
业务若要长期引用固定内容,应保存不可变键或准确版本 ID;版本管理不能自动决定业务应该使用哪一版,也不等同于独立备份。
9.6 对象存储强一致,是否就能避免并发覆盖并保证数据库记录同步完成?
考察重点:读写可见性、条件写入、缓存和跨系统事务的不同范围。
相关内容:一致性与覆盖条件。
参考回答
**不能。强一致性不阻止无条件覆盖,也不提供对象存储与业务数据库之间的原子事务。**应先限定具体服务和访问路径,再根据要求使用其条件写入能力,例如不存在才写或匹配预期 ETag 才替换。
条件要在写入时原子判断。数据库记录仍需独立的状态与恢复机制;经过缓存的访问、未完成的请求和权限配置传播,也要分别考虑。
9.7 私有对象的预签名 URL 是否天然一次性,是否可以替代业务授权?
考察重点:签名请求的范围、凭据有效性、重复使用和业务资源归属。
相关内容:访问权限与预签名 URL。
参考回答
**通常不是一次性的,也不能替代生成链接前的业务授权。**持有者可在方法、对象、有效期和签名者权限等条件满足时使用链接,可能重复请求;长期凭据不能因此暴露给客户端。
服务端应先核对用户对具体对象的操作权限,再生成有限范围的链接。需要一次性消费时,还要增加状态控制,而不是仅设置较短有效期。
10. 参考资料
- Amazon S3:数据模型与一致性
- Amazon S3:对象键、对象元数据
- Amazon S3:多段上传、CompleteMultipartUpload
- Amazon S3:版本管理、条件写入
- Amazon S3:预签名 URL
- Cloudflare R2:一致性与缓存
- MinIO RELEASE.2025-04-22T22-12-26Z
来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明