先把结论摆在这里:你手机里删了照片但存储空间没怎么变,和云厂商天天讲的块存储、文件存储、对象存储,其实是同一套底层逻辑在不同尺度上的呈现。很多人一听到这三个词就头大,觉得是运维和架构师才需要懂的东西,实际上只要你用过手机、用过网盘、往电脑里插过U盘,你已经在跟这三种存储打交道了,只是没人把它们放一起讲清楚。
这篇文章我就用最直白的方式,把块存储、文件存储、对象存储拆开揉碎。不堆术语,不讲虚的,讲清楚三件事:它们分别是什么、各自解决什么问题、实际项目里到底怎么选。最后再顺手解开两个最近被问爆的场景——手机"假删除"问题和日志链路里 widely 出现的"alloy → loki → 对象存储桶 → grafana"部署模式。搞懂这些,你以后看任何存储相关的技术方案,心里都会有个清晰的坐标系。
1. 一个"删不掉的空间"带我重新理解了存储分层
1.1 文件管理器里的"删除",只是第一层
先还原一个极度常见的场景:你拿着一台平板或者手机,打开相册,把最近拍的几百张照片和视频全部选中、删除。系统弹窗提示"已删除到最近删除"。你去系统设置里看存储空间,好家伙,可用空间几乎没变,甚至有时候还会变得更少了。
这时候绝大多数人的第一反应是:这手机是不是卡了?或者系统有 Bug?我明明删了 5 个 G 的视频,为什么空间没回来?
答案其实藏在存储的工作方式里。存储世界并不是你打开文件管理器看到的那层"文件夹 + 文件"那么简单。你在界面上操作的"删除",落在底层存储设备上,可能只是一条"我标记这块区域为可复用"的指令,并不等于"把这块区域立刻擦干净腾出来"。这个现象和本文要讲的块存储、文件存储、对象存储有直接关系——不同的存储类型,对"删除"这两个字的理解完全不一样。
1.2 从 LBA 到文件系统:存储世界其实有三层抽象
想在十分钟内搞懂三大存储类型,你得先知道一个最底层的概念:LBA,Logic Block Address,逻辑块地址。
不管是手机里的闪存、电脑里的固态硬盘、还是数据中心里的磁盘阵列,物理存储介质被抽象成一个个固定大小的"块"。以最常见的 4K 扇区为例,整块磁盘就是一大串编号从 0 到 N 的块。拿到一块裸盘,你要读写数据,就得告诉存储设备"请把数据写到第 x 号块",这就是 LBA 读写。注意,这个时候你的脑子里不应该有"文件""目录""文件名"这些概念,只有"块"和"地址"。
块存储,就是这一层的东西。你看到的是一块块连续或独立的"裸磁盘",谁来写都行,写什么格式都行。操作系统要在这块裸盘上,再盖一层"文件系统",把第 x 号块、第 y 号块组织成一个个文件和目录,这才有了你熟悉的 C 盘、D 盘、Mac 的访达、Linux 的 /home。文件存储,就是这一层的产物。而对象存储,它是完全跳过了"文件系统"这个思维模式,用另一套"桶 + 对象"的模型来解决更大的问题。
所以你看,同样是"存个文件",背后的存储类型完全不是一回事。现在我们把这三层拆开细讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 块存储:把存储切成砖头,让操作系统自己盖房
2.1 块存储的本质是 LBA,不是文件名
块存储最让人困惑的地方在于:它根本不关心你存进去的是什么。一块 100G 的云硬盘(比如云厂商的 EVS/EBS),格式化之后挂载到服务器上,你既可以把它当成一个装文件的磁盘,也可以把它当成 Oracle 数据库的裸设备,还可以做成 LVM 逻辑卷再切成多个小分区。对于块存储系统来说,它只保证一件事:当你告诉它"我要读第 10000 号块",它能把这个块的内容原样返回给你。
块存储的典型物理形态是什么?你电脑里那块 NVMe 固态硬盘,数据中心里的 RAID 磁盘组,SAN(存储区域网络)里通过光纤通道映射给服务器的 LUN,云服务器上挂载的云硬盘。这些东西本质上都是"块设备"。
在上层基础架构中,块存储最常见的两种打开方式:
- 文件系统直接落地:把块设备格式化成 ext4、XFS、NTFS 等文件系统,变成服务器的一个分区或整盘挂载点。
- 裸设备直通:数据库软件不经过文件系统,直接以原始块的方式读写设备。早期 Oracle 数据库特别喜欢这么干,就是为了绕过文件系统的额外开销,榨干磁盘的随机读写性能。
2.2 为什么数据库和虚拟机磁盘非它不可
市面上存储类型这么多,为什么搞虚拟化和数据库的人都对块存储情有独钟?核心原因就两条:延迟低、语义简单。
数据库的写入路径是极度频繁的随机小 I/O。一条 UPDATE 语句可能要写一个数据页,这个页在盘的哪个位置,数据库自己早有算计。它需要的是"我说往哪写就往哪写,你快点给我写完"的直接反馈。块存储没有目录树查询、没有文件锁管理、没有 inode 分配这些中间层开销,天然就适合这种低延迟高并发的随机读写场景。
虚拟机磁盘也是同样道理。你在 OpenStack 或者 VMware 里创建一个虚机,背后往往是一块块存储提供的 qcow2/raw 盘。虚拟机的操作系统自己在里面格式化文件系统,它不需要你上层存储再给它做一层"文件解释",它只需要一块"能像本地盘一样读写的虚拟磁盘"。
你可能也听说过"超融合""分布式块存储"这些词。Ceph 的 RBD、云厂商的云硬盘、开源社区的 Longhorn,本质上就是把多台服务器的本地盘聚合起来,对外提供一块"无限大的可以随时扩容的虚拟磁盘"。这在 Kubernetes 里面被抽象成 StorageClass 里的一个类型,动态给 Pod 创建块卷。
2.3 上手实操:在 Linux 上创建一个逻辑卷
纸上谈兵没意思,我直接给你一个可以亲手验证的块存储操作。在 Linux 服务器上,我们找两块闲置磁盘做 LVM(逻辑卷管理),这就是一个最典型的"块存储组合再造"过程:
bash复制# 创建物理卷,把 /dev/sdb 和 /dev/sdc 变成 LVM 能管理的 PV
pvcreate /dev/sdb /dev/sdc
# 创建卷组,名字叫 vg_data
vgcreate vg_data /dev/sdb /dev/sdc
# 从卷组里切出一个 100G 的逻辑卷,这就是一个可用的块设备
lvcreate -L 100G -n lv_data vg_data
# 在逻辑卷上创建文件系统并挂载
mkfs.ext4 /dev/vg_data/lv_data
mkdir /data
mount /dev/vg_data/lv_data /data
你发现没有?我们在"存储系统"层面做的事,纯粹是在拼积木:把物理块组合成更大的块,再把逻辑块切成自己想要的尺寸。至于 /data 目录下怎么组织文件,是 ext4 文件系统的事,不归块存储管。
块存储的短板也很明显:它本身不提供"多台机器共享一个目录"的能力。你想让两台服务器同时读写同一个块设备,不好意思,普通文件系统根本不支持跨节点锁,硬搞就是数据损坏。于是才有了下一层——文件存储。
3. 文件存储:目录树是人类和计算机的共同语言
3.1 文件系统在块之上的"装修层"
文件存储跟块存储的关系,就像精装修房和毛坯房的关系。块存储给了你红砖和水泥,文件存储在红砖和水泥之上给你隔出了客厅、卧室,还贴好了门牌号。
文件系统做的事,核心就三件:
- 用 inode 记录一个文件的元信息(大小、权限、修改时间)以及它占用了哪些数据块。
- 用目录项(dentry)把"文件名"和 inode 关联起来,形成你看到的目录树。
- 提供一套 POSIX 语义的接口:open、read、write、close、rename、append、文件锁。
有了文件系统,人类才能用"路径 + 文件名"的方式去指代数据。比如 /var/log/nginx/access.log,你一看就知道这是 nginx 的访问日志。你用编程语言写 file.write("hello"),操作系统会自动帮你找到文件对应的数据块,找到空间分配新块,更新元数据。这一整套机制,就是文件存储的核心价值。
3.2 NFS/SMB:文件存储的共享交付模式
单机上的文件系统(ext4、NTFS、APFS)只是文件存储的基础形态。一旦你有多台机器要互相共享文件,就需要把文件系统"网络化"。这时两大协议站出来:NFS(网络文件系统)和 SMB/CIFS(服务器消息块协议)。
NFS 是 Linux/Unix 世界的事实标准,SMB 是 Windows 网络共享的主力(macOS 两边都兼容)。
我在家里搭了一个 NAS 小主机,把两块机械硬盘做了 raid1,开了一个 NFS share。三台电脑、一部手机要互相传文件时,不需要再 AirDrop 或者微信传了,直接挂载同一份目录:
bash复制# 在 Linux 客户端上挂载 NAS 上的共享目录
mount -t nfs 192.168.1.10:/volume1/shared /mnt/shared
在 Windows 上,你在文件资源管理器里输入 \\192.168.1.10\shared,就能看到同一个目录。这就是文件存储和块存储最大的不同:文件存储允许"多台机器同时访问同一个目录树",并且提供基本的文件锁和权限控制,避免多个人同时改一个文件改出冲突。
3.3 家用 NAS 和分布式文件存储,原理是同一件事
这里插一句。很多人觉得自己搭的 NAS 和公司里的"分布式文件存储"是两种东西,其实底层逻辑是一模一样的。家用 NAS 大多数用 Samba/NFS 把一块本地盘共享出去。公司里的 GlusterFS、Lustre、CephFS,也还是文件存储,只是为了支撑海量数据,把数据分散到很多台服务器上,但对外仍然画出一个统一的目录树。
以 CephFS 为例,它内部会把目录树的一部分元数据交给管理节点处理,文件数据则按条带切成若干份分布到不同的数据节点上。你从客户端的角度看,看到的还是一个 /mnt/cephfs 目录,还是熟悉的 ls、cp、mv。这种"不管底层多复杂,上面给你一个标准健壮的目录接口"的设计,就是文件存储对付"多节点共享"的核心思想。
文件存储倒是好用了,但它的局限在于:单目录并发的元数据操作会成为瓶颈,而且随着文件数量增长到亿级,那棵"目录树"会变得非常难以维护。这时候,第三种思路出现了。
4. 对象存储:一个巨大的 HTTP 键值仓库
4.1 对象存储为什么不叫"文件存储"
对象存储(Object Storage)可能是三大存储里被误解最深的一个。很多人以为对象存储就是"把你网盘里的文件放到云上",其实不是。对象存储的设计哲学,跟文件系统完全是两个方向:文件系统执念于"目录树",对象存储则完全放弃目录树,只保留一个扁平化的命名空间。
对象存储的基本单位叫"对象"(Object),它由三部分组成:
- 数据本身:可以是照片、视频、日志、备份文件,什么都可以。
- 元数据:一套自定义的属性,比如 Content-Type、存储级别、自定义标签。
- 全局唯一的 ID:一般是一个 URL,比如
https://bucket.s3.amazonaws.com/images/2025/01/cover.jpg。
你往对象存储里写一个文件,实际上是在执行一个 HTTP PUT 请求;读文件,是 HTTP GET 请求;删除,是 DELETE 请求。对象存储不提供 ls 这种查看目录的功能,也不支持你打开一个文件然后在中间某个位置追加写数据。对象是不可变的,你想改一个对象的内容,唯一的办法是把它整个删掉再重新上传一个同名对象覆盖。
你可能注意到了,对象存储没有任何"目录"的概念。你看那些 S3 URL 里面有斜杠 /images/2025/01/cover.jpg,这只是用户自己在 key 里约定俗成地加了斜杠,看起来像目录而已。存储系统本身根本不管你 key 怎么写,它只负责通过分布式哈希算法把不同的 key 分散到不同的存储节点上。这也是对象存储能做到几乎无限扩展的原因——它在架构上不存在一个"中央目录服务器"作为瓶颈。
4.2 S3 API 已成为事实标准
聊对象存储避不开 S3,不是因为它有多先进,而是因为它出现得早、生态最完善。S3 是 AWS 在 2006 年推出的对象存储服务,定义了 Bucket(桶)和 Object(对象),提供了一套完整的 RESTful API。这么多年下来,几乎所有的对象存储产品都在兼容 S3 API:
- 开源自建的:MinIO、Ceph RGW、SeaweedFS。
- 公有云的:阿里云 OSS、腾讯云 COS、华为云 OBS,也全部提供 S3 兼容接口。
这套 API 有个特别厉害的地方:它对上层应用来说就是简单的 HTTP 接口,任何语言、任何平台都能轻松调用。你不用在自己机器上挂载任何特殊驱动,一个 SDK 一段代码就能读写:
python复制import boto3
s3 = boto3.client(
's3',
endpoint_url='https://your-bucket-endpoint', # 兼容 S3 的服务地址
aws_access_key_id='AKIA...',
aws_secret_access_key='...'
)
# 上传本地文件到对象存储
s3.upload_file(
'/temp/backup.sql',
'my-bucket',
'backup/2025/01/backup.sql'
)
# 下载对象到本地
s3.download_file(
'my-bucket',
'backup/2025/01/backup.sql',
'/temp/restore.sql'
)
在 Kubernetes、日志系统、大数据生态里,S3 兼容 API 几乎成了对象存储的"普通话"。K8s 的备份工具 Velero、日志系统 Loki、数据湖方案 Iceberg/Hudi,配置文件里都能看到 s3://bucket/prefix 这样的路径。你只要有一台支持 S3 API 的对象存储服务,就能无缝对接整个云原生生态。
4.3 对象存储便宜,是因为它放弃了一些东西
很多刚接触对象存储的人都有个疑惑:为什么对象存储在云上比同容量的云硬盘便宜那么多?这背后是有明确原因的。
文件系统为了保证 POSIX 语义,需要维护目录树、记录文件锁、支持随机写,这些都要消耗不小的计算和存储资源。对象存储把这些复杂语义全砍了。你只能整体写、整体读、整体删,不支持随机写,不支持文件锁,甚至可以先提供一个弱一致性的读取结果(现在的产品大多保证最终一致性)。
用通俗的话讲:文件系统是一间装修精细的客房,你要住进去每天有人给你整理房间,自然贵;对象存储是一个巨大的集装箱堆场,货物整箱进、整箱出,没人帮你拆箱分类,自然便宜。
正因为它牺牲了文件系统的灵活性,换来了极致的水平扩展能力和极低的存储成本,对象存储成了"海量数据存储"的首选落点。静态资源放这,因为 CDN 可以直接回源拉取;家目录都丢这里做冷备,因为便宜。稍后我会讲到的 Loki 日志链路,日志数据长期保留部分之所以往对象存储桶里塞,看重的也正是这个"便宜 + 无限扩展"。
5. 选型不是二选一:一个系统可以三种都占
5.1 一张思维导图式的决策清单
把三大存储彻底讲完之后,我们落到实打实的问题上:我就一个项目,到底该用哪个?
我的建议是:不要上来就对着性能参数表纠结,先回答自己三个问题。
第一问:你需要的是"一块硬盘",还是"一个共享文件夹",还是"一个海量资源的仓库"?
- 如果是操作系统要装在这里跑,虚拟机要在这上面建虚机,数据库数据文件要落盘,选块存储。
- 如果是多台机器要协作修改同一批文件,用户上传的附件直接被应用加工后喂给业务系统,选文件存储。
- 如果是海量静态文件、备份、归档、日志,数据一旦写入很少修改,选对象存储。
第二问:你的数据规模到了什么量级?
文件数量在几百万以下,单机文件系统算得很舒服;几千万乃至上亿文件的时候,单机目录树就受不了了。要么上分布式文件系统,要么直接切对象存储。我个人观察,现代互联网公司很多新业务根本不再纠结文件系统,而是"能不落本地盘就不落本地盘,全部丢对象存储"。
第三问:你的数据需要被随机改写吗?
照片、视频、日志、备份,绝大多数都是"写一次读多次"的天然只读数据,对象存储的首选。数据库数据则要求极低延迟的随机读写和强一致,块存储才是正确选择。
我把三个类型的关键差异整理成一张表,方便你对照:
| 对比维度 | 块存储 | 文件存储 | 对象存储 |
|---|---|---|---|
| 数据模型 | 按块地址读写 | 目录树 + 文件路径 | Bucket + Object + Key |
| 访问协议 | SCSI、iSCSI、NVMe over Fabric | NFS、SMB/CIFS | HTTP RESTful,S3 API |
| 典型使用场景 | 数据库、虚拟机磁盘、Kubernetes PV | 文档共享、NAS、多机共享 | 静态资源、日志、备份、数据湖 |
| 随机写 | 支持,性能强 | 支持 | 不支持,只能整体覆盖 |
| 多机共享 | 极难,需要特殊集群文件系统 | 天然支持 | 天然支持 |
| 扩展性 | 受限于单卷,扩展靠分布式块存储拼盘 | 受限于元数据性能 | 近乎无限水平扩展 |
| 单位成本 | 高 | 中 | 低 |
| 典型产品 | 云硬盘、Ceph RBD、SAN | NFS、GlusterFS、CephFS | MinIO、AWS S3、阿里云 OSS |
5.2 混合架构实例:一个在线视频平台的存储设计
真实的大型系统从来都是三种存储混着用,因为它们解决的问题根本不在一个层面。
我拿一个在线教育平台举例。这个平台的核心是视频课程:
视频上传后,平台并不会把视频内容直接写进数据库,而是推到对象存储桶里。对象存储里放着所有课程原片、转码之后的多种清晰度版本,以及封面图片、课件 PDF 这类静态资源。对象存储负责"海量 + 便宜"。视频分发时,CDN 直接从对象存储拉取回源,十几年攒下来几 PB 的课程视频,靠对象存储轻松顶住。
平台的业务数据库跑在 RDS 上,底层是块存储。订单、用户信息、课程关联关系都在这上面。块存储负责"高性能 + 事务性保障"。平台内部有很多部门,比如课程审核团队,需要多人同时查看同一批待审核的视频和文档,他们挂载的是一个文件存储共享目录。文件存储负责"协同工作"。
同一个平台,三种存储各司其职,互不冲突。你要是有机会去看大型云厂商的客户案例,大概率都能看到这种混合部署的影子。
6. 追热点速解:手机存储"假删除"和 Loki 日志链路
6.1 小米平板删除文件后为什么存储还在
回到开头那个问题:为什么删了平板里的照片视频,可用空间却不涨反跌?
真相是:文件管理器和操作系统看到的"删除",和存储介质看到的"抹除",并不是同一个操作。
你在手机相册里删除照片,文件系统实际上做了什么?它找到这个文件对应的 inode,减少链接计数,把这块数据空间从"已占用"标记为"空闲可复用"。但是数据本身还老老实实呆在闪存颗粒上。对于用户来说,这个文件已经"不存在"了,因为未来的写入操作可以覆盖这块区域。可是这块区域现在还占着位置,没有真正从设备里"消失"。
更麻烦的是,手机上的文件管理器和相册普遍有"最近删除"功能。你第一下点的删除,实际上是把它挪进了一个"回收桶",这个桶里内容继续占空间,而且相册的索引数据库还持有这些文件的记录。所以删完照片空间反而变得更小了——因为这些照片不但还在,又多建了一层索引。
接下来还有一个隐藏环节:对固态存储设备来说,即使文件系统已经把块标记为"空闲",设备固件还要收到 TRIM 指令才知道"这些块可以回收了"。TRIM 不是实时触发的,往往是电量充足、设备空闲时才执行。这就是为什么你清完存储空间之后,有时候要等一阵子才能看到可用空间真正回升,甚至重启一次才生效。
说到底,这背后正是块存储和文件存储纠缠不清的典型案例:应用层往文件系统里写了一堆目录和文件,文件系统在底层的"块"上做标记,存储控制器管着真正的"块擦写",一层一层转达,延迟了空间释放。搞清楚这个机制,你再遇到"清理完存储不释放"的问题就不会慌了:等设备闲下来执行 TRIM,想彻底回收空间就多做一次"深度清理"或者重启。
6.2 二进制全链路:alloy → loki → 对象存储桶 → grafana
最后聊一个偏运维的热点链路:alloy → loki → 对象存储桶 → grafana。这套东西现在在云原生日志圈子里出镜率极高,它同时打通了块存储、文件存储、对象存储的交叉使用,非常值得展开。
这条链路解决的核心痛点是:业务容器日志散落在各个节点上,出事的时候你不想一台台机器翻日志,希望把所有日志集中到一处,既能长期保存,又能快速搜索。
它的分工是这样的:
Alloy(Grafana Alloy)是一个用二进制直接跑起来的采集器。它负责在每台服务器上读取容器和系统日志文件,做简单过滤、打标签,然后推给 Loki。以前更多用 Promtail,现在 Grafana 官方把它合并进了 Alloy,部署就是下个二进制文件启动,配置文件指定采集路径和 Loki 地址,简单直接。
Loki 是这套链路的大脑。它采用一种"日志标签索引 + 日志内容压缩块"的设计:只给日志的 label(比如服务名、实例 IP、级别)建索引,不把日志全文塞进索引里。日志正文被压缩成一个又一个 chunk,这些 chunk 要落地保存。
那 chunk 存在哪?本地磁盘只是初期缓存,长期保存的数据通常都会配置到对象存储桶里。因为日志这个东西多起来非常恐怖,一天几个 TB 很正常。你不可能无限扩充本地磁盘,而且本地磁盘没法做到多副本异地容灾。对象存储桶则完美契合——日志又是典型的"只写一次、几乎不改、必须便宜、最好永不过期"的数据,正好是对象存储的甜区。
Loki 接入 S3 兼容对象存储桶的配置并不复杂,以本地部署 MinIO 为例:
yaml复制storage_config:
aws:
s3:
endpoint: minio:9000
bucketnames: loki-data
access_key_id: minioadmin
secret_access_key: minioadmin
insecure: true
s3forcepathstyle: true
tsdb_shipper:
active_index_directory: /loki/index
cache_ttl: 24h
shared_store: s3
insecure: true 表示用 HTTP 而不是 HTTPS,本地测试常用。s3forcepathstyle: true 表示访问桶时使用 http://endpoint/bucket/key 这种路径式风格,MinIO 和自建 Ceph RGW 默认要开这个。
最后,Grafana 接入 Loki 数据源,你就能在一个面板里完成日志查询、指标展示、告警联动。整套链路里对象存储的存在,让日志数据有了一个既便宜又无限大的"终点站"。这也是为什么现在很多人哪怕业务上完全不碰对象存储,只要上了这套日志方案,都会顺手自建一个 MinIO 或者用云上对象存储服务做底座。
关于这套链路我踩过两个小坑,提醒你注意:
第一,桶要提前建好。Loki 不会自动创建对象存储桶,你配置里写的 bucketnames 对应的桶如果不存在,写入会一直报错。我习惯在部署脚本里先跑一条 mc mb local/loki-data 把桶建好。
第二,Loki 的索引和数据块可以分开落存储。索引建议放在本地磁盘或者单独的快速卷上,这样查询体验好;数据块长期丢对象存储。这也是为什么 Loki 的配置里既有本地目录又有 S3 配置,两个角色各管一段,别混为一谈。
搞懂这套链路之后你会发现,对象存储桶在这里承担的角色,跟我们在第 4 章讲的"海量数据终点站"完全一致。存储选型不是靠感觉,而是靠你对数据访问模式的理解。这块地基打牢了,后面你再看云原生存储、数据湖、日志系统这些概念,都会轻松很多。
