块存储通过设备接口暴露可寻址的数据块,对象存储通过接口暴露带名字的对象。仅凭这两个分类,不能决定耐久性、一致性、共享能力或价格。应先列出应用真正需要执行的操作,再核对具体服务的契约。文件系统、数据库和兼容层可以增加行为,但不能让所有底层接口自动变得可互换;一个路径能够被打开,并不证明它符合应用的恢复要求。
比较实际支持的操作
块卷可以先格式化为文件系统,也可以由软件直接管理数据布局。块服务并不一定预装文件系统。应用可以进行小范围覆盖而不必替换整块卷,但共享访问仍然需要协调。把一个卷同时连接到多台机器,不会让普通单机文件系统自动变成集群文件系统。数据库能否恢复,还依赖块接口之上的写入顺序、同步和持久性约定,不能把能够写入等同于断电以后仍然正确。
对象通过桶或其他服务定义的命名空间寻址。普通 S3 通用桶写入会替换整个对象;读取却可以指定字节范围,所以“对象存储每次必须读完整个对象”并不正确。分段上传把多个部分组装成一个完成的对象,不等于通用文件系统的随机写契约。特殊产品还可能提供追加或其他更新能力,因此必须核对选中的桶类型与接口,不能把下面的小模型当成所有产品永远不变的规则。
对象存储通常适合媒体、导出结果和备份文件,让许多客户端按需读取。块存储通常适合数据库文件与虚拟机磁盘。但这只是常见工作负载,并不是互斥分类:数据库可以专门围绕对象接口设计,共享文件系统也可以建立在块设备之上。选择之前应测量请求大小、并发量、恢复过程,以及请求与数据传输的完整成本,不能只比较每月每单位容量的标价。
坏掉的样子
不要默认对象存储采用最终一致性。Amazon S3 对对象写入、删除及对应读取和列举提供强一致性。客户端手里保留旧下载结果,仍然可能看见旧内容,但这不能证明重新向服务读取也会得到旧值。分发缓存、跨区域复制和跨多个对象的业务更新又是不同边界,必须各自检查保证。把这些现象都叫作“对象存储有一致性窗口”,会让排查方向从一开始就偏离事实。
成功的 S3 PUT 不会暴露半个上传对象。请求失败或结果不明时,应确认实际状态,不能直接推断新版本被截断。单个对象的原子替换也无法防止丢失更新:两个客户端可能读取同一旧值,再分别覆盖它。条件请求可以在接受更新之前比较校验标识,但要遵循服务文档中的条件语义。ETag 并不总是内容校验和,更不是把多个键包进同一个事务的承诺。
生命周期删除与历史版本保留应通过真实恢复演练检验。打开版本控制不等于已经有独立备份,在程序里记一个版本数字,也不等于保存了所有旧载荷。大量小对象即使占用字节不多,请求开销仍可能主导成本。兼容挂载层能够接受数据库文件名,同样需要证明它满足锁、写入顺序与失败恢复语义,才能作为合适的数据库存放位置。
快速验证
保存为 storage_semantics.py,用 Python 3.9 或更新版本执行 python3 storage_semantics.py。这是使用虚构字节的独立内存模型,不访问服务,不测文件系统持久性,也不涉及网络延迟与计费。它只把明确写进类定义的规则变成可检查的操作,方便分辨几种常被混淆的保证。
"""In-memory API model, not an implementation of a storage service."""
class BlockVolume:
def __init__(self, data):
self.data = bytearray(data)
def write(self, offset, chunk):
if offset < 0 or offset + len(chunk) > len(self.data):
raise ValueError("write outside fixed volume")
self.data[offset:offset + len(chunk)] = chunk
class ObjectStore:
def __init__(self):
self.current = {}
def put(self, key, data, expected=None):
old = self.current.get(key)
version = old[1] if old else 0
if expected is not None and expected != version:
raise ValueError("version conflict")
self.current[key] = (bytes(data), version + 1)
return version + 1
def get(self, key):
return self.current[key]
block = BlockVolume(b"ABCDEF")
block.write(2, b"XX")
assert block.data == b"ABXXEF"
try:
block.write(5, b"too long")
except ValueError:
pass
else:
raise AssertionError("out-of-range write accepted")
assert block.data == b"ABXXEF"
store = ObjectStore()
assert store.put("report", b"ABCDEF", expected=0) == 1
cached = store.get("report")
assert store.put("report", b"XYZ", expected=1) == 2
assert store.get("report") == (b"XYZ", 2)
assert cached == (b"ABCDEF", 1)
try:
store.put("report", b"stale overwrite", expected=cached[1])
except ValueError:
pass
else:
raise AssertionError("stale conditional update accepted")
assert store.get("report") == (b"XYZ", 2)
assert store.get("report")[0][1:3] == b"YZ"
print("PASS: bounded overwrite; complete replacement; cached copy differs; stale condition rejected")
块模型最初有六字节,覆盖中间两字节后,相邻内容保持不变。越界写入被拒绝,原数据没有因此变化。对象模型从第一版的六字节内容,整体替换成第二版的三字节内容;新的读取立即得到替换结果。另一个事先保存的元组仍然包含第一版,因为应用保留了自己的旧副本。这是本地缓存陈旧,不是服务读取出现最终一致性延迟。
使用旧版本发起的条件覆盖失败,最后的断言确认当前对象仍然完整不变。对已经取回的字节做切片,只演示选择一个范围,不是对真实 HTTP Range 请求的测试。末尾 PASS 证明的是这些显式模型规则,不证明厂商实现、跨客户端并发、历史载荷保留、崩溃原子性或灾难恢复。正式使用之前,还应通过所选服务的真实客户端,验证相关操作及失败处理流程,明确哪些结论来自模型,哪些来自系统本身。
来源
- Amazon S3 data model,对象操作与强一致性,已核验 2026-10-15。
- S3 PutObject,完整对象写入与条件请求,已核验 2026-10-15。
- Amazon EBS,块卷与应用管理的文件系统,已核验 2026-10-15。