跳到主要内容
约 13 分钟7 张卡

对象存储

对象存储以带有标识和元数据的对象保存内容,应用通过对象键读取、写入或删除它。图片、视频、附件和备份可以放在对象存储中,数据库则保存文件归属、业务状态和对象位置等信息。

理解对象存储,重点是把对象键、内容、版本和访问权限分开。一个看起来像路径的键,不等于本地文件系统的目录;成功上传一个对象,也不等于关联的数据库记录已经一起提交。

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. 参考资料​

来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明