全部文章 笔记与思考

对象、文件与块存储:云计算的三种基础存储怎么选?

从寻址、元数据、读写链路、一致性与数据冗余出发,讲清对象存储、文件存储和块存储的工作原理与选型方法。

对象、文件与块存储:云计算的三种基础存储怎么选?

把数据放进云里,看起来只是“找个地方存起来”,实际却要先回答一个更基础的问题:应用希望以什么方式访问这些数据?

云计算里最常见的三类基础存储——对象存储(Object Storage)、文件存储(File Storage)和块存储(Block Storage)——保存的最终都是二进制数据,真正的区别在于:数据如何被组织、通过什么接口访问,以及由谁负责管理文件系统。

对象存储、文件存储与块存储的基本模型

先看结论:

对比维度 对象存储 文件存储 块存储
基本单位 对象 文件和目录 固定大小的数据块
常见访问方式 HTTP API、SDK NFS、SMB、文件系统接口 挂载为块设备,由操作系统读写
数据组织 Bucket + Key,通常是扁平命名空间 目录树 只提供块地址,本身没有文件和目录
谁管理文件系统 存储服务 文件存储服务 使用者或操作系统
核心优势 容量大、扩展容易、耐久性高 多客户端共享、兼容传统应用 低延迟、控制力强、适合频繁随机读写
典型场景 图片、视频、备份、日志、数据湖 共享目录、内容管理、开发目录 云主机系统盘、数据库、事务型应用

一、三种存储,本质上差在哪里?

三者并不一定对应三种不同的物理硬盘。底层介质可能同样是 HDD、SSD,甚至同一套分布式存储集群;不同的是系统向上暴露的抽象层。

三种存储从应用寻址到底层数据的访问链路

对象存储已经理解“一个对象及其 Key”;文件存储进一步提供目录、文件名、权限和锁;块存储反而暴露更低层的逻辑块地址,把文件系统语义留给操作系统或数据库。抽象越高,服务替使用者管理的事情越多;抽象越低,使用者得到的控制力越强。

对象存储:用唯一的 Key 找到一份完整数据

对象存储把每份数据保存为一个“对象”。一个对象通常由三部分组成:

  • 数据本身:例如图片、视频、压缩包或日志文件;
  • 元数据:例如内容类型、创建时间、缓存策略和自定义标签;
  • 唯一标识:通常称为 Key,用来定位对象。

应用一般通过 HTTP API 或 SDK 执行 PUT、GET、DELETE 等操作。控制台里看到的“文件夹”往往只是 Key 前缀形成的视觉效果,例如 images/2026/avatar.png 仍然可以只是一个完整的 Key,并不代表后端真的存在层层嵌套的目录。

一次写入是怎样完成的? 当客户端发起 PUT bucket/key 时,请求通常先经过鉴权和路由层。系统根据 Bucket 和 Key 找到负责该命名空间的分区,为对象分配版本号和存储位置,再把较大的对象切成若干片段。数据片段会附带校验和,并通过多副本或纠删码分散到不同磁盘、节点乃至可用区。达到服务设定的持久化条件后,元数据索引才会把这个 Key 指向新版本并向客户端返回成功。具体顺序因产品而异,但核心都是把“名字到数据位置的映射”和“真正的数据”分开管理。

读取时则走相反方向:系统先由 Key 找到对象版本和片段位置,再从一个或多个节点读取数据,校验内容后返回。若发现片段损坏,后台任务可以利用其他副本或冗余片段重建它。这也是对象存储容易横向扩展的原因:一个 Key 可以被稳定地路由到某个分区,不同对象之间通常不需要共享目录锁。

这种模型很适合海量、相对独立的数据。系统可以把不同对象分散到大量节点上,容量扩展和高耐久性更容易实现,使用者也不必管理磁盘和文件系统。

但对象存储不是一块可以随意修改任意字节的远程硬盘。更新对象时,常见做法是重新上传整个对象;重命名也可能需要“复制新对象,再删除旧对象”。虽然部分服务提供分段上传、范围读取等能力,它仍不适合直接承载传统数据库的数据文件。

这种限制并非单纯“功能没做完”,而是对象模型主动做出的取舍。把对象视为一个完整版本,可以让系统更容易计算校验和、生成版本、跨节点复制并处理失败重试;代价是失去了文件系统那种随时覆盖中间几个字节的语义。

适合它的场景包括:

  • 网站静态资源、用户上传的图片和视频;
  • 备份、归档和软件安装包;
  • 日志、数据湖和机器学习数据集;
  • 需要通过互联网直接分发的大文件。

文件存储:把云端目录挂载到多台机器

文件存储保留了人们最熟悉的目录树:目录下面有子目录和文件,文件有名称、权限、修改时间等属性。应用可以通过 NFS、SMB 等协议,把远端文件系统挂载到本地,然后像访问普通目录一样使用它。

路径是怎样变成数据的? 当应用调用 open("/team/report.md") 时,文件系统需要逐级解析 /、team 和 report.md。目录项把名称映射到 inode 或文件 ID,元数据记录文件大小、权限、时间戳以及数据所在位置。权限检查和锁处理完成后,客户端获得文件句柄,后续的 read、write 才能访问真正的数据块。

为了避免每次读写都跨网络往返,客户端通常会缓存目录项、属性和文件内容,并通过租约、锁、版本号或缓存失效通知维持多客户端之间的一致性。这里存在一个天然拉扯:缓存越积极,读取越快;但多个客户端同时修改时,协调成本也越高。NFS、SMB 以及不同云产品提供的锁和一致性语义并不完全相同,因此“能挂载”不等于“任何共享写入方式都安全”。

它的最大价值不是“能存文件”——三种存储都能保存文件内容——而是提供了共享的文件系统语义。多台云主机可以访问同一套目录,旧应用也常常不需要改造代码就能迁移到云上。

代价是文件系统需要维护目录、权限、锁和元数据。面对大量小文件或极高并发时,元数据操作可能成为瓶颈;不同产品对 POSIX 语义、文件锁和一致性的支持也可能不同,迁移关键应用前需要确认。

小文件问题也来自这里:创建一百万个 1 KB 文件,不只是写入约 1 GB 数据,还包含一百万次文件创建、目录更新、权限记录和时间戳修改。扩展型文件系统往往会把元数据服务与数据服务分开,并对目录或文件 ID 分片,但热点目录仍可能把大量请求集中到少数元数据节点。

常见场景有:

  • 多台服务器共同访问的共享目录;
  • 内容管理系统、媒体制作和团队工作区;
  • 用户主目录、开发环境和企业文件共享;
  • 依赖本地路径、无法直接改成对象 API 的传统应用。

块存储:给操作系统一块“云硬盘”

块存储把空间切分成许多固定大小的数据块,并把它作为一块裸设备交给云主机。设备本身只认识块地址,不认识“订单表”“图片”或“目录”;操作系统需要先在上面创建文件系统,数据库也可以按自己的格式直接管理这些块。

一个块请求里有什么? 操作系统向虚拟磁盘发出的核心信息通常可以简化为“从逻辑块地址 LBA 开始,读取或写入多长的数据”。云平台维护一张卷映射表,把这段逻辑地址翻译到后端存储节点上的物理片段。写入可以先进入日志或缓存,再复制到其他节点;只有满足确认策略后,系统才把完成结果返回给主机。精简置备只在真正写入时分配后端空间,扩容本质上则是扩大这张逻辑地址映射。

块设备不知道上层正在更新一张数据库表还是一个文件。它可以保证某次块操作的完成状态,并提供写屏障或刷新能力,但文件系统日志、数据库 WAL、事务提交和崩溃恢复仍由上层负责。如果应用没有正确发出 flush,或者快照时内存里的事务尚未落盘,即使块存储本身没有丢数据,恢复出来的文件系统或数据库也可能不一致。

站在云主机里看,它很像本地磁盘,因此适合低延迟、频繁覆盖和随机读写。云主机系统盘、数据库数据盘、消息队列和需要稳定 IOPS 的事务型系统,通常优先考虑块存储。

与此同时,使用者要负责分区、格式化、扩容后的文件系统调整以及数据保护。一个普通块存储卷通常由一台主机独占使用;即使产品支持多主机挂载,也必须配合支持并发访问的集群文件系统或应用,否则很容易损坏数据。

块存储快照通常利用写时复制(Copy-on-Write)或重定向写(Redirect-on-Write):创建快照时不立即复制整块磁盘,而是先保存块映射关系,后续只记录发生变化的块。因此快照可以很快创建,但它通常只保证卷崩溃一致性。要获得应用一致性快照,还需要先冻结文件系统、刷新缓存,或让数据库进入合适的备份状态。

一句话概括三者的边界:

对象存储交付“可通过 Key 访问的对象”,文件存储交付“可共享的目录树”,块存储交付“可自行管理的磁盘空间”。

二、可靠性、一致性和性能是怎样实现的?

讨论云存储时,几个词经常被混在一起:

概念 真正回答的问题
耐久性(Durability) 已经确认写入的数据,长期丢失的概率有多低?
可用性(Availability) 现在发起请求,服务能否及时响应?
一致性(Consistency) 多个客户端在什么时刻能看到同一个最新状态?
原子性(Atomicity) 一次修改会完整生效,还是可能暴露中间状态?

一个系统可以耐久性很高,但在网络故障时暂时不可用;也可以始终响应,却让某些客户端短时间读到旧版本。因此,“数据不会丢”和“任何时候都能读到最新数据”是两种不同保证。

多副本与纠删码。 多副本直接保存若干份完整数据,读取和故障切换简单,但容量成本较高。纠删码把数据编码成 $k$ 个数据片和 $m$ 个校验片,只要仍有足够片段就能恢复原数据,空间效率更高,但编码、重建和小块更新会消耗更多 CPU、网络与磁盘 I/O。对象存储的大对象和冷数据常适合纠删码;追求低延迟的热数据常偏向副本。具体实现并不由“对象、文件、块”这个名称决定,同一产品也可能按存储层级采用不同策略。

写成功到底意味着什么? 分布式系统通常不会等待所有副本都完成才响应,而是依据确认数量、故障域和写入日志等规则判断。确认得越保守,故障后的数据风险越低,但写延迟可能越高。后台修复还必须持续扫描校验和,在磁盘或节点损坏后重新补齐冗余;仅仅“最初写了三份”并不能保证冗余永远健康。

三种接口对外承诺的原子性也不同:

类型 通常能依赖的边界 不能想当然的部分
对象存储 单个对象的创建或替换通常以完整版本出现 多对象事务、目录重命名和跨 Key 原子更新
文件存储 文件与目录操作由协议提供,并可能支持锁、原子重命名 不同客户端缓存下的严格 POSIX 语义、任意应用的并发写安全
块存储 已完成的块读写以及产品声明的写入顺序、刷新语义 文件或数据库事务的一致性,这属于上层职责

这些保证必须以具体产品文档为准。例如,许多现代对象存储已经为单对象操作提供强一致性,但这并不会自动带来跨多个对象的事务能力。

性能也不是简单的三档排名。 一次 I/O 的延迟大致由客户端处理、网络往返、元数据查询、排队和介质访问共同组成。IOPS 描述每秒能完成多少次操作,吞吐量描述每秒能传输多少数据;在未碰到网络或服务上限时,可以粗略理解为:

$$ \text{吞吐量} \approx \text{IOPS} \times \text{平均 I/O 大小} $$

数据库常发出大量 4 KiB 或 16 KiB 的随机 I/O,更关心延迟和 IOPS;备份或数据分析一次传输数 MiB,往往更关心吞吐量。对象存储单次请求延迟可能更高,却可以并行读取大量对象,聚合吞吐反而可能远高于一块块存储卷。

访问需求 更值得优先考虑 原因
单次读写一张完整图片 对象存储 接口简单,可直接扩展到海量对象
多台服务器共同编辑目录中的文件 文件存储 原生提供目录、权限和共享访问
数据库进行大量小块随机写入 块存储 延迟更低,可由数据库和文件系统精细控制
一次扫描 PB 级日志或训练数据 对象存储 吞吐可以通过大量对象并行扩展
应用必须使用本地文件路径 文件存储或块存储 可以挂载为文件系统,无须改成对象 API

最后,高耐久性不等于备份。副本和纠删码主要处理硬件故障,却会忠实同步误删除、错误覆盖和应用逻辑错误。版本控制、快照和跨区域复制各自解决一部分问题,但重要数据仍应保留权限隔离、恢复流程经过验证的独立备份。

三、30 秒完成一次初步选型

选型时至少要同时看五件事:

  1. 接口:应用使用对象 API、文件路径,还是原始块设备?
  2. 共享与并发:数据由一台主机独占,还是多台主机同时读写?
  3. 访问模式:以整对象顺序读写为主,还是频繁随机更新小范围数据?
  4. 语义要求:是否依赖文件锁、原子重命名、事务刷新或严格一致性?
  5. 运维边界:谁负责扩容、文件系统、快照、备份和恢复演练?
flowchart TD A[应用要怎样访问数据?] --> B{能使用对象 API 吗?} B -->|能,主要整份读写| O[对象存储] B -->|不能| C{多台主机要共享同一目录吗?} C -->|需要| F[文件存储] C -->|不需要| D{需要低延迟随机读写
或自行管理文件系统吗?} D -->|需要| K[块存储] D -->|不确定| E[先确认应用接口和访问模式]

现实系统很少只选一种。例如,一个电商系统可以把数据库放在块存储上,把多台应用服务器共享的配置或生成文件放在文件存储上,再把商品图片、日志和备份放进对象存储。它们不是互相替代的三个等级,而是解决不同问题的三种工具。

最后记住一个最实用的判断方法:**不要先问哪种存储“最好”,先问应用已经会说哪种语言。**说 HTTP API,就从对象存储开始;说目录和文件路径,就看文件存储;需要一块由操作系统或数据库完全掌控的磁盘,就选块存储。