10.1 块存储 / 文件存储 / 对象存储
一句话
三者不是 “贵贱之分”,而是三种访问语义:块是裸设备(文件系统你自己管)、文件是目录树(POSIX 语义)、对象是扁平命名空间 + HTTP API(不能原地改,只能整对象替换);选错的代价不是性能,而是 “这个用法根本不存在”。
三种语义,一张表看清
| 维度 | 块存储 | 文件存储 | 对象存储 |
|---|---|---|---|
| 访问单位 | 扇区 / 4KB 块 | 文件(open/read/write) | 整个对象(GET/PUT) |
| 协议 | iSCSI / NVMe-oF / 云盘挂载 | NFS / SMB / POSIX | HTTP(S) + S3 API |
| 能否多机共享 | 不能(除非集群文件系统) | 能(NFS/SMB) | 能(天然 HTTP) |
| 目录语义 | 无(要自己格式化) | 有(目录树 + 权限) | 无(桶 + 扁平 key) |
| 随机写 | 支持 | 支持(锁语义弱) | 不支持(整对象替换) |
| 扩展方式 | 加盘 / 加 RAID / 换更大卷 | 加节点(元数据是瓶颈) | 近乎无限(分片 + 扁平) |
| 典型延迟 | 0.1–1ms | 0.5–5ms(网络) | 10–100ms(首字节) |
性能与成本量级
| 指标 | 块 | 文件 | 对象 |
|---|---|---|---|
| IOPS | 高(万级–百万级) | 中(元数据受限) | 低(按请求计费) |
| 吞吐 | 高(顺序读) | 中高 | 高(大对象顺序) |
| 单位成本(元/GB/月) | 高 | 中 | 最低 |
| 元数据操作 | 无 | 瓶颈所在 | 桶级 list 有限制 |
一条经验:块最贵但最快,对象最便宜但最慢,文件在中间且元数据先撑不住。
用途对照
| 场景 | 选什么 | 为什么 |
|---|---|---|
| 数据库数据文件、WAL | 块 | 需要低延迟随机写与 fsync |
| 容器节点盘、系统盘 | 块 | 独占、可格式化 |
| 多 Pod 共享配置/上传目录 | 文件(NFS) | 需要 POSIX 与多机挂载 |
| 图片、视频、备份、日志归档 | 对象 | 海量、便宜、HTTP 直出 |
| 数据湖 / 湖仓 | 对象 | 计算存储分离、弹性 |
落到数字:三种存储的量级
| 指标 | 块(云盘 SSD) | 文件(NFS) | 对象(S3) |
|---|---|---|---|
| 随机读延迟 | 0.1–0.3ms | 0.5–2ms | 20–80ms(首字节) |
| 4K 随机 IOPS | 数千–数万 | 数百–数千 | 按请求计费,通常不做随机读 |
| 顺序吞吐 | 100–500MB/s | 100–1000MB/s | 数十–数百 MB/s(单连接) |
| 单位成本 | 高(约 1 元/GB/月量级) | 中 | 低(约 0.1 元/GB/月量级) |
# 块:4K 随机读的真实 IOPS(fio 未在本机跑,命令与判据如下)
fio --name=r --rw=randread --bs=4k --iodepth=32 --filename=/mnt/test --size=1G --runtime=30
# 判据:iops 这一行;与云盘规格对比,低于 50% 先查队列深度与卷类型
反模式
| 反模式 | 后果 |
|---|---|
| 把数据库文件放对象存储 | 没有随机写与 fsync,事务无法保证 |
| 用块存储做 “共享盘”(两个节点同时挂) | 文件系统互相踩坏(RWO 也是单节点,见 7.7 存储卷与持久化) |
| 用 NFS 存百万级小文件 | 元数据操作打满,ls 都要几十秒 |
| 用对象存储做低延迟随机读写 | 每改一个字节要重传整个对象 |
| 只看单价不看请求费 | 小文件场景下请求费远超容量费(见 5.6 云成本结构) |
| 用“云盘”承载共享文件 | 多节点无法安全挂载,数据互相踩坏 |
三条铁律
🔑 先问 “怎么访问”,再问 “多少钱”:随机写要块,共享目录要文件,海量冷数据要对象。
🔑 对象存储没有 “改”,只有 “换”;需要原地更新的数据不该放对象。
🔑 文件存储的瓶颈永远是元数据,不是带宽。
自检
Q1 一个团队把 MySQL 的数据目录放到 NFS 上,为什么一定会出问题?
▸ 答案:因为 MySQL 依赖 fsync 与文件锁的严格 POSIX 语义,而 NFS 的锁与缓存一致性是 “弱语义”:多客户端缓存、延迟写入、锁租约都可能让 InnoDB 认为 “已落盘” 实际没有,崩溃恢复直接损坏。数据库必须用块设备(本地盘或云盘)。
Q2 对象存储为什么 “不能原地修改”?
▸ 答案:因为它的数据模型是 “不可变对象 + 元数据索引”:PUT 会写一个全新对象(新版本),元数据指向新位置,旧对象由生命周期清理。这样设计才能做多副本/纠删码、跨区复制与无限扩展。原地改一个字节意味着重写整个对象,所以适合 “一次写、多次读”。
Q3 什么时候该用文件存储而不是对象存储?
▸ 答案:需要目录语义与多机并发写时:多 Pod 共享上传目录、传统应用直接读写文件路径、需要 rename/flock 之类语义。代价是元数据瓶颈与扩展上限。如果只是 “存文件、按 URL 取”,对象存储更便宜也更容易扩展。
Q4 为什么 “都放对象存储” 会成为反模式?
▸ 答案:因为对象存储的每次访问都有 HTTP 开销(10–100ms),且按请求计费。把高频小文件(如配置、缩略图元数据)放进去,延迟与请求费双高。正确分层是:热数据用块/文件缓存,冷数据落对象,用 CDN/缓存层挡在前面。
Q5 三种存储能混用吗?给一个典型组合。
▸ 答案:能,而且常见:计算节点用块盘(系统盘 + 数据库盘),共享配置/上传用文件存储,静态资源与备份落对象存储,前面再加 CDN。分层判据是 “访问频率 × 延迟要求 × 单位成本”:热、温、冷各放一层(见 10.9 存储选型)。
Q6 为什么 “对象存储便宜” 在小文件场景下可能不成立?
▸ 答案:因为它按请求计费:小文件每次 GET/PUT 都是一次请求,加上 list 与生命周期操作,请求费常常超过容量费。判断要看 “请求数 × 单价” 与 “容量 × 单价” 谁更大,而不是只看元/GB。