你每天都在和存储打交道——写数据到 MySQL、读 Redis 缓存、往 S3 上传文件。但你有没有想过:为什么 SSD 比 HDD 快这么多?为什么数据库用 B+ 树而不是哈希表?为什么 LSM 树写快读慢?为什么云数据库要计算存储分离?
本章是整个系列的”地图”。不会深入任何一种存储系统的内部实现(那是后续章节的任务),而是从宏观视角俯瞰存储系统的完整图景:硬件有什么特性、操作系统如何抽象、数据怎么组织、引擎怎么工作、分布式怎么扩展。理解了这幅全景图,后续每一章的学习就有了锚点。
一、存储层级金字塔
1.1 从 CPU 到磁带:存储的层级结构
计算机的存储系统是一个经典的层级结构,每一层之间都有数量级的性能差异:
存储系统设计的核心矛盾:速度与容量的权衡。L1 Cache 比 HDD 快 1000 万倍,但容量只有其十亿分之一。所有存储系统的设计决策——缓存、预取、压缩、索引——本质上都是在弥合层级之间的性能鸿沟。
1.2 延迟数量级对比
理解存储系统,首先要对各级延迟有直觉:
| 存储介质 | 延迟 | 吞吐量 | 每GB成本 | 随机访问 |
|---|---|---|---|---|
| L1 Cache | ~1 ns | ~1 TB/s | ~$10,000 | 极快 |
| L2 Cache | ~4 ns | ~500 GB/s | ~$5,000 | 极快 |
| L3 Cache | ~10 ns | ~200 GB/s | ~$1,000 | 极快 |
| DRAM | ~100 ns | ~50 GB/s | ~$10 | 快 |
| NVMe SSD | ~10 μs | ~7 GB/s | ~$0.5 | 较快 |
| SATA SSD | ~100 μs | ~0.5 GB/s | ~$0.3 | 一般 |
| HDD | ~10 ms | ~0.2 GB/s | ~$0.03 | 慢 |
| 网络存储 | ~100 μs–1 ms | ~10 GB/s | ~$0.1 | 取决于协议 |
一个有用的思维实验:如果 CPU 周期是 1 秒,那么 L1 Cache 访问需要 1 秒,内存访问需要 100 秒,NVMe SSD 访问需要约 3 个月,HDD 访问需要约 4 年。这就是为什么存储系统的核心设计目标永远是减少慢速层的访问次数。
1.3 SSD 延迟分解
SSD 的访问延迟并非铁板一块,内部有多个环节:
| SSD 延迟环节 | 典型耗时 | 说明 |
|---|---|---|
| NVMe 命令提交 | ~1 μs | SQ/CQ 队列操作 |
| FTL 映射查找 | ~2 μs | 逻辑地址→物理地址 |
| 闪存页读取 | ~25 μs | 单页 16KB |
| 闪存页编程 | ~300 μs | 单页写入 |
| 擦除块擦除 | ~3 ms | 整块 4–8MB |
| GC 搬迁延迟 | ~50 μs | 后台垃圾回收引入 |
SSD 读取延迟约 25–50 μs,但写入延迟因 GC 和擦除操作波动极大。FTL 的写放大(Write Amplification, WA)通常在 1.5–3x 之间,意味着用户写 1MB,闪存实际写入 1.5–3MB。
1.4 存储层级对系统设计的影响
存储层级的巨大性能差异直接决定了系统设计的核心策略:
# 存储层级的核心设计策略strategies = { "缓存(Caching)": "将热数据放在更快的层级,减少慢速层访问", "预取(Prefetching)": "预测未来需要的数据,提前加载到快速层", "批处理(Batching)": "将多个小 I/O 合并为大 I/O,摊销延迟开销", "压缩(Compression)": "减少数据量,降低传输和存储开销", "索引(Indexing)": "用额外的空间换取查找速度,避免全量扫描", "顺序化(Sequential)": "将随机 I/O 转化为顺序 I/O,利用磁盘带宽",}二、存储系统的分类
2.1 按介质分类
2.2 按访问模式分类
| 访问模式 | 特点 | 典型系统 | 数据结构 |
|---|---|---|---|
| 块存储 | 固定大小块,随机读写 | 本地磁盘、EBS、Ceph RBD | 块设备 |
| 文件存储 | 目录树结构,POSIX 语义 | ext4、NFS、CephFS | 树形目录 |
| 对象存储 | 扁平命名空间,HTTP 访问 | S3、MinIO、Ceph RGW | 哈希索引 |
| 键值存储 | 简单 KV 接口,高性能 | RocksDB、LevelDB | LSM/B+ 树 |
| 文档存储 | 结构化文档,灵活 Schema | MongoDB | B 树 |
2.3 按读写模式分类
| 读写模式 | 特点 | 典型场景 | 代表引擎 |
|---|---|---|---|
| 读优化 | 读取快,写入需原地更新 | OLTP 点查 | InnoDB (B+ 树) |
| 写优化 | 写入快,读取需合并 | 日志、时序 | RocksDB (LSM 树) |
| 分析优化 | 批量扫描快,点查慢 | OLAP 分析 | Parquet (列存) |
| 混合型 | 读写均衡 | HTAP | TiKV (LSM + 缓存) |
三、存储引擎架构
3.1 通用存储引擎架构
无论具体实现如何,大多数存储引擎都遵循相似的分层架构:
3.2 B 树引擎 vs LSM 树引擎
这是存储系统中最根本的设计取舍:
| 维度 | B 树引擎(InnoDB) | LSM 树引擎(RocksDB) |
|---|---|---|
| 写入方式 | 原地更新(In-place Update) | 追加写入(Out-of-place Update) |
| 写放大 | 中等(页面分裂、日志) | 较高(Compaction 重写) |
| 读放大 | 低(O(log N) 树遍历) | 较高(多层级查找) |
| 空间放大 | 中等(页面碎片) | 较高(过期数据待 Compaction) |
| 最佳场景 | 读多写少 | 写多读少 |
| 并发控制 | Latch + 锁 | Copy-on-Write |
| 典型系统 | InnoDB、PostgreSQL | RocksDB、LevelDB、Cassandra |
B 树和 LSM 树的取舍本质上是 RUM 猜想 的体现:Read-cost、Update-cost、Memory-cost 三者不可兼得,优化其中两个必然牺牲第三个。详见 Ch17 存储引擎对比。
3.3 存储引擎的核心组件
// 存储引擎核心组件的抽象接口struct StorageEngine { // 写路径组件 WAL *wal; // 预写日志,保证持久性 MemTable *memtable; // 内存中的有序写入缓冲 MemTable *immutable; // 正在刷盘的只读 MemTable
// 读路径组件 BlockCache *cache; // 块级缓存 BloomFilter *bloom; // 布隆过滤器,减少无效 I/O
// 持久化组件 SSTable *levels[]; // 多层级 SSTable 文件 Compactor *compactor; // 后台 Compaction 线程
// 事务组件 VersionSet *versions; // 版本管理(MVCC) LockManager *locks; // 并发控制};四、存储系统的性能模型
4.1 三个放大因子
存储系统的性能可以用三个”放大因子”来衡量:
| 放大因子 | 定义 | 公式 | 影响 |
|---|---|---|---|
| 写放大(Write Amplification) | 实际写入磁盘的数据量 / 用户写入的数据量 | WA = 磁盘写入量 / 用户写入量 | SSD 寿命、写入延迟 |
| 读放大(Read Amplification) | 一次用户读取触发的磁盘 I/O 次数 | RA = 磁盘读取次数 / 用户读取次数 | 读取延迟 |
| 空间放大(Space Amplification) | 磁盘上实际占用的空间 / 用户数据大小 | SA = 磁盘占用 / 用户数据量 | 存储成本 |
# 写放大示例:LSM 树的 Compaction# 假设用户写入 1MB 数据user_write = 1 # MB
# Level 0 → Level 1: 重写 1MB# Level 1 → Level 2: 重写 10MB# Level 2 → Level 3: 重写 100MBtotal_disk_write = user_write * (1 + 10 + 100) # = 111MB
write_amplification = total_disk_write / user_write # = 111xprint(f"写放大: {write_amplification}x")4.2 顺序 vs 随机 I/O
这是理解存储系统设计的另一个关键维度:
| 特性 | 顺序 I/O | 随机 I/O |
|---|---|---|
| HDD 性能 | ~200 MB/s | ~0.5 MB/s(400x 差距) |
| SSD 性能 | ~5 GB/s | ~0.1 GB/s(50x 差距) |
| 适用场景 | 日志、Compaction、备份 | 索引查找、点查 |
| 优化策略 | 批量写入、Group Commit | 缓存、预取、索引 |
LSM 树的核心设计思想就是将随机写入转化为顺序写入:用户写入先进入内存的 MemTable,积累到一定大小后顺序刷盘为 SSTable。代价是读取时需要检查多个层级(读放大)。详见 Ch6 LSM 树深入。
4.3 新兴存储技术:CXL 与计算存储
传统存储层级存在一个根本矛盾:DRAM 容量有限,SSD 延迟太高。CXL(Compute Express Link)和计算存储正在打破这一限制:
| 技术 | 原理 | 延迟 | 适用场景 |
|---|---|---|---|
| CXL 内存 | 通过 CXL 协议扩展远端 DRAM | ~200–400 ns | 大内存数据库、缓存 |
| CXL Cache | 远端设备缓存一致性 | ~100–200 ns | 多节点共享缓存 |
| 计算存储 | SSD 内嵌处理器,就近计算 | 省去数据搬运 | 压缩、过滤、扫描 |
| 近存计算 | DRAM 旁挂 FPGA/ASIC | ~50 ns | 向量化聚合 |
CXL 内存延迟比本地 DRAM 高 2–4 倍,不适合作为 Buffer Pool 的主存储。更适合存放冷数据或作为内存扩展池。计算存储目前生态尚不成熟,仅在大规模扫描场景(如数据湖过滤)有显著收益。
4.4 写路径的关键步骤
一个写入请求从应用到持久化,经历以下关键步骤:
# 写路径关键步骤及延迟估算write_path_steps = { "1. WAL 写入": "~10 μs (NVMe 顺序写)", "2. MemTable 更新": "~0.1 μs (内存跳表插入)", "3. WAL 刷盘(提交)": "~10 μs (fsync/fdatasync)", "4. MemTable → SSTable": "~1 ms (后台 Flush, 异步)", "5. Compaction": "~10 ms–1 s (后台, 异步)",}# 同步延迟:步骤 1+2+3 ≈ 20 μs(用户感知的写入延迟)# 异步延迟:步骤 4+5 在后台执行,不阻塞用户写入4.5 存储性能基准
以下是常见存储操作的延迟参考值,来自 Peter Norvig 的经典数据(已更新至 2024 年硬件水平):
| 操作 | 延迟 | 说明 |
|---|---|---|
| L1 缓存引用 | 1 ns | CPU 内部 |
| L2 缓存引用 | 4 ns | CPU 内部 |
| 互斥锁加/解锁 | 20 ns | 纯内存操作 |
| 主内存引用 | 100 ns | DRAM 访问 |
| 压缩 1KB 数据 (ZSTD) | 3 μs | 单核 |
| 从 SSD 顺序读 1MB | 10 μs | NVMe |
| 网络往返(同机房) | 100 μs | 1Gbps |
| 从 SSD 顺序读 1MB | 200 μs | SATA |
| 从磁盘顺序读 1MB | 5 ms | HDD |
| 磁盘寻道 | 10 ms | HDD 随机访问 |
五、从单机到分布式的演进
5.1 存储系统的演进路线
5.2 演进的核心驱动力
| 阶段 | 核心驱动力 | 解决的问题 | 引入的新问题 |
|---|---|---|---|
| 本地磁盘 | 数据持久化 | 断电不丢失 | 单盘故障 = 数据丢失 |
| RAID | 数据冗余 | 单盘故障容忍 | 重建慢、扩展性差 |
| 分布式文件系统 | 水平扩展 | 容量/性能瓶颈 | 一致性、复杂性 |
| 对象存储 | 海量非结构化数据 | 规模与成本 | 一致性模型弱 |
| 计算存储分离 | 弹性伸缩 | 资源利用率 | 网络延迟、一致性 |
| 资源分解 | 细粒度资源分配 | 资源浪费 | 硬件复杂性 |
5.3 存储系统的 CAP 取舍
分布式存储系统必须在 CAP 三者之间做出取舍:
| 系统 | 一致性 (C) | 可用性 (A) | 分区容忍 (P) | 取舍说明 |
|---|---|---|---|---|
| Ceph RADOS | 强一致 | 可用 | 容忍 | 用 Quorum 保证一致性 |
| MinIO | 强一致 | 可用 | 容忍 | 纠删码 + Quorum |
| S3 | 最终一致 | 高可用 | 容忍 | 放弃强一致换可用性 |
| Cassandra | 可调一致 | 高可用 | 容忍 | 可调 Quorum 级别 |
| Aurora | 强一致 | 可用 | 容忍 | Quorum + 6 副本 |
六、本系列的学习路径
6.1 知识依赖关系
6.2 各章核心问题
| 章节 | 核心问题 | 关键概念 |
|---|---|---|
| Ch2 磁盘与 SSD | 为什么 SSD 写入会放大? | NAND 闪存、FTL、擦除块 |
| Ch3 文件系统 | 文件系统如何保证崩溃一致性? | 日志、CoW、JBD2 |
| Ch4 块层与 I/O 栈 | I/O 请求如何从应用到磁盘? | blk-mq、IO 调度、io_uring |
| Ch5 B 树深入 | B+ 树如何处理并发与分裂? | 页面组织、Latch、前缀压缩 |
| Ch6 LSM 树深入 | LSM 树如何优化写入? | MemTable、SSTable、Compaction |
| Ch7 写路径 | 一个写请求经历了什么? | WAL、Group Commit、刷盘 |
| Ch8 读路径 | 一个读请求经历了什么? | Bloom Filter、Block Cache、预取 |
| Ch9 WAL 与崩溃恢复 | 崩溃后数据如何恢复? | ARIES、LSN、检查点 |
| Ch10 缓冲池 | 如何减少磁盘访问? | LRU 变体、脏页刷盘、双写 |
| Ch11 压缩与编码 | 如何减少存储空间和 I/O? | RLE、字典编码、ZSTD |
| Ch12 MVCC | 如何实现无锁并发读? | Undo Log、快照隔离、VACUUM |
| Ch13 列式存储 | 为什么分析场景用列存? | Parquet、向量化、Zone Map |
| Ch14 RAID 与纠删码 | 如何在多盘间冗余数据? | RAID 级别、Reed-Solomon |
| Ch15 分布式文件系统 | 如何跨节点组织存储? | Ceph CRUSH、RADOS |
| Ch16 对象存储 | 如何存储海量非结构化数据? | MinIO、S3、纠删码 |
| Ch17 存储引擎对比 | 不同引擎的取舍是什么? | RUM 猜想、InnoDB vs RocksDB |
| Ch18 云原生存储 | 云上存储架构如何变革? | 计算存储分离、日志即数据库 |
| Ch19 综合实战 | 如何手写一个存储引擎? | MiniLSM、WAL、Compaction |
七、实战:观察存储层级
7.1 Linux 存储栈观察工具
# 查看块设备信息lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,ROTA# ROTA=1 表示旋转设备(HDD), ROTA=0 表示非旋转设备(SSD)
# 查看 I/O 调度器cat /sys/block/sda/queue/scheduler
# 查看页面缓存统计cat /proc/meminfo | grep -i "cache\|buffer"
# 使用 iostat 观察 I/O 统计iostat -x 1 5
# 使用 perf 观察 I/O 延迟分布perf stat -e 'block:block_rq_issue,block:block_rq_complete' -a sleep 107.2 数据库存储行为观察
-- MySQL: 观察 InnoDB 状态SHOW ENGINE INNODB STATUS\G
-- 观察 Buffer Pool 命中率SHOW STATUS LIKE 'Innodb_buffer_pool_read%';-- Innodb_buffer_pool_read_requests /-- (Innodb_buffer_pool_read_requests + Innodb_buffer_pool_reads)
-- 观察页面刷新统计SHOW STATUS LIKE 'Innodb_data_written';SHOW STATUS LIKE 'Innodb_os_log_written';-- PostgreSQL: 观察缓冲区统计SELECT sum(heap_blks_read) AS heap_read, sum(heap_blks_hit) AS heap_hit, sum(heap_blks_hit) / NULLIF(sum(heap_blks_hit + heap_blks_read), 0) AS cache_hit_ratioFROM pg_statio_user_tables;
-- 观察 WAL 写入量SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) AS wal_written;7.3 存储延迟直方图
# 使用 bcc 工具观察 I/O 延迟分布# biolatency - I/O 延迟直方图/usr/share/bcc/tools/biolatency 1 10
# 输出示例:# usecs : count distribution# 0 -> 1 : 0 | |# 2 -> 3 : 0 | |# 4 -> 7 : 0 | |# 8 -> 15 : 12 |****** |# 16 -> 31 : 45 |********************** |# 32 -> 63 : 89 |****************************************|# 64 -> 127 : 23 |*********** |# 128 -> 255 : 5 |** |八、总结
| 主题 | 核心要点 | 关键词 |
|---|---|---|
| 存储层级 | 从 L1 Cache 到磁带 6 个数量级延迟差异,驱动所有设计决策 | 延迟金字塔, 层级结构 |
| 分类体系 | 按介质、访问模式、读写模式三维度理解存储系统 | 介质分类, 访问模式 |
| 引擎架构 | B 树 vs LSM 树是最根本的取舍,核心是读优化 vs 写优化 | B 树, LSM 树 |
| 性能模型 | 写放大、读放大、空间放大三因子衡量存储效率 | 放大因子, RUM |
| 演进路线 | 从本地磁盘到计算存储分离,每步都在解决前一步引入的问题 | 存算分离, 演进 |
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






