mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3051 字
9 分钟
存储系统全景:从磁盘到分布式
2025-05-19

你每天都在和存储打交道——写数据到 MySQL、读 Redis 缓存、往 S3 上传文件。但你有没有想过:为什么 SSD 比 HDD 快这么多?为什么数据库用 B+ 树而不是哈希表?为什么 LSM 树写快读慢?为什么云数据库要计算存储分离?

本章是整个系列的”地图”。不会深入任何一种存储系统的内部实现(那是后续章节的任务),而是从宏观视角俯瞰存储系统的完整图景:硬件有什么特性、操作系统如何抽象、数据怎么组织、引擎怎么工作、分布式怎么扩展。理解了这幅全景图,后续每一章的学习就有了锚点。

一、存储层级金字塔#

1.1 从 CPU 到磁带:存储的层级结构#

计算机的存储系统是一个经典的层级结构,每一层之间都有数量级的性能差异:

graph TB subgraph 存储层级["存储层级金字塔 — 越往上越快越贵,越往下越慢越便宜"] L1["L1 Cache<br/>~1ns / ~64KB"] L2["L2 Cache<br/>~4ns / ~256KB"] L3["L3 Cache<br/>~10ns / ~8MB"] RAM["主内存 DRAM<br/>~100ns / ~128GB"] NVMe["NVMe SSD<br/>~10μs / ~2TB"] SATA_SSD["SATA SSD<br/>~100μs / ~4TB"] HDD["HDD 磁盘<br/>~10ms / ~20TB"] TAPE["磁带<br/>~10s / ~PB级"] end L1 --> L2 --> L3 --> RAM --> NVMe --> SATA_SSD --> HDD --> TAPE
Important

存储系统设计的核心矛盾:速度与容量的权衡。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取决于协议
Note

一个有用的思维实验:如果 CPU 周期是 1 秒,那么 L1 Cache 访问需要 1 秒,内存访问需要 100 秒,NVMe SSD 访问需要约 3 个月,HDD 访问需要约 4 年。这就是为什么存储系统的核心设计目标永远是减少慢速层的访问次数

1.3 SSD 延迟分解#

SSD 的访问延迟并非铁板一块,内部有多个环节:

SSD 延迟环节典型耗时说明
NVMe 命令提交~1 μsSQ/CQ 队列操作
FTL 映射查找~2 μs逻辑地址→物理地址
闪存页读取~25 μs单页 16KB
闪存页编程~300 μs单页写入
擦除块擦除~3 ms整块 4–8MB
GC 搬迁延迟~50 μs后台垃圾回收引入
Note

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 按介质分类#

graph TB subgraph 存储介质分类 MEMORY["内存存储<br/>Redis / Memcached<br/>断电丢失 / 极快"] SSD["SSD 存储<br/>NVMe / SATA<br/>持久化 / 较快"] HDD["磁盘存储<br/>HDD<br/>持久化 / 慢"] TAPE["磁带存储<br/>LTO-9<br/>归档 / 极慢"] end MEMORY --> VOLATILE["易失性存储"] SSD --> PERSISTENT["持久化存储"] HDD --> PERSISTENT TAPE --> PERSISTENT

2.2 按访问模式分类#

访问模式特点典型系统数据结构
块存储固定大小块,随机读写本地磁盘、EBS、Ceph RBD块设备
文件存储目录树结构,POSIX 语义ext4、NFS、CephFS树形目录
对象存储扁平命名空间,HTTP 访问S3、MinIO、Ceph RGW哈希索引
键值存储简单 KV 接口,高性能RocksDB、LevelDBLSM/B+ 树
文档存储结构化文档,灵活 SchemaMongoDBB 树

2.3 按读写模式分类#

读写模式特点典型场景代表引擎
读优化读取快,写入需原地更新OLTP 点查InnoDB (B+ 树)
写优化写入快,读取需合并日志、时序RocksDB (LSM 树)
分析优化批量扫描快,点查慢OLAP 分析Parquet (列存)
混合型读写均衡HTAPTiKV (LSM + 缓存)

三、存储引擎架构#

3.1 通用存储引擎架构#

无论具体实现如何,大多数存储引擎都遵循相似的分层架构:

graph TB subgraph 存储引擎架构 CLIENT["客户端请求"] --> API["API 层<br/>Get/Put/Delete/Scan"] API --> WRITE["写路径<br/>WAL → MemTable → SSTable"] API --> READ["读路径<br/>Cache → Bloom → 磁盘"] WRITE --> WAL["WAL 日志<br/>崩溃恢复保障"] WRITE --> MEM["MemTable<br/>内存中的有序结构"] MEM --> FLUSH["Flush<br/>内存→磁盘"] FLUSH -> SST["SSTable 文件<br/>磁盘上的有序数据"] SST --> COMPACT["Compaction<br/>合并与清理"] READ --> CACHE["Block Cache<br/>热点数据缓存"] READ --> BLOOM["Bloom Filter<br/>减少无效查找"] READ --> SST WAL --> RECOVERY["崩溃恢复<br/>ARIES 算法"] end

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、PostgreSQLRocksDB、LevelDB、Cassandra
Important

B 树和 LSM 树的取舍本质上是 RUM 猜想 的体现:Read-cost、Update-cost、Memory-cost 三者不可兼得,优化其中两个必然牺牲第三个。详见 Ch17 存储引擎对比。

graph TB subgraph RUM猜想["RUM 猜想:三者不可兼得"] R["Read 优化<br/>B+ 树"] --- TRADE1["写放大 + 空间碎片"] U["Update 优化<br/>LSM 树"] --- TRADE2["读放大 + 空间放大"] M["Memory 优化<br/>紧凑编码"] --- TRADE3["读放大 + 写放大"] end R -.->|"不可兼得"| U U -.->|"不可兼得"| M M -.->|"不可兼得"| R

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: 重写 100MB
total_disk_write = user_write * (1 + 10 + 100) # = 111MB
write_amplification = total_disk_write / user_write # = 111x
print(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缓存、预取、索引
Note

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向量化聚合
Warning

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 nsCPU 内部
L2 缓存引用4 nsCPU 内部
互斥锁加/解锁20 ns纯内存操作
主内存引用100 nsDRAM 访问
压缩 1KB 数据 (ZSTD)3 μs单核
从 SSD 顺序读 1MB10 μsNVMe
网络往返(同机房)100 μs1Gbps
从 SSD 顺序读 1MB200 μsSATA
从磁盘顺序读 1MB5 msHDD
磁盘寻道10 msHDD 随机访问

五、从单机到分布式的演进#

5.1 存储系统的演进路线#

graph LR subgraph 单机时代 LOCAL["本地磁盘<br/>ext4 / XFS"] RAID["RAID<br/>冗余与性能"] end subgraph 分布式时代 DFS["分布式文件系统<br/>Ceph / Gluster"] OBJ["对象存储<br/>MinIO / S3"] end subgraph 云原生时代 CLOUD["计算存储分离<br/>Aurora / Neon"] DISAGG["资源分解<br/>远端内存 / 智能网卡"] end LOCAL --> RAID --> DFS --> OBJ --> CLOUD --> DISAGG

5.2 演进的核心驱动力#

阶段核心驱动力解决的问题引入的新问题
本地磁盘数据持久化断电不丢失单盘故障 = 数据丢失
RAID数据冗余单盘故障容忍重建慢、扩展性差
分布式文件系统水平扩展容量/性能瓶颈一致性、复杂性
对象存储海量非结构化数据规模与成本一致性模型弱
计算存储分离弹性伸缩资源利用率网络延迟、一致性
资源分解细粒度资源分配资源浪费硬件复杂性

5.3 存储系统的 CAP 取舍#

分布式存储系统必须在 CAP 三者之间做出取舍:

系统一致性 (C)可用性 (A)分区容忍 (P)取舍说明
Ceph RADOS强一致可用容忍用 Quorum 保证一致性
MinIO强一致可用容忍纠删码 + Quorum
S3最终一致高可用容忍放弃强一致换可用性
Cassandra可调一致高可用容忍可调 Quorum 级别
Aurora强一致可用容忍Quorum + 6 副本

六、本系列的学习路径#

6.1 知识依赖关系#

graph TB subgraph 基础["Part 1: 物理与系统"] CH1["Ch1 存储全景"] --> CH2["Ch2 磁盘与SSD"] CH2 --> CH3["Ch3 文件系统"] CH3 --> CH4["Ch4 块层与IO栈"] end subgraph 结构["Part 2: 数据结构"] CH4 --> CH5["Ch5 B树深入"] CH4 --> CH6["Ch6 LSM树深入"] CH5 --> CH7["Ch7 写路径"] CH6 --> CH7 CH5 --> CH8["Ch8 读路径"] CH6 --> CH8 end subgraph 机制["Part 3: 核心机制"] CH7 --> CH9["Ch9 WAL与崩溃恢复"] CH8 --> CH10["Ch10 缓冲池"] CH10 --> CH11["Ch11 压缩与编码"] CH9 --> CH12["Ch12 MVCC与版本管理"] end subgraph 分布式["Part 4: 分布式与演进"] CH11 --> CH13["Ch13 列式存储"] CH9 --> CH14["Ch14 RAID与纠删码"] CH14 --> CH15["Ch15 分布式文件系统"] CH15 --> CH16["Ch16 对象存储"] CH13 --> CH17["Ch17 存储引擎对比"] CH12 --> CH17 CH16 --> CH18["Ch18 云原生存储"] CH17 --> CH18 CH18 --> CH19["Ch19 综合实战"] end

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 10

7.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_ratio
FROM 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
演进路线从本地磁盘到计算存储分离,每步都在解决前一步引入的问题存算分离, 演进

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

存储系统全景:从磁盘到分布式
https://blog.souloss.com/posts/storage/storage-overview/
作者
Souloss
发布于
2025-05-19
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时