mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2009 字
6 分钟
磁盘与 SSD 物理结构
2025-10-10

HDD 一次随机访问要 10ms,SSD 只要 0.1ms——100 倍的差距。但 SSD 也有自己的代价:写入前必须先擦除整个块,实际写入磁盘的数据量远大于用户提交的数据量。一块标称 1TB 的 SSD,在极端写入模式下可能提前耗尽寿命。

这些数字不是凭空出现的。它们来自物理层——HDD 的磁头寻道、盘片旋转,SSD 的 NAND 闪存擦写、FTL 映射。存储系统的每一层设计决策,都受制于这些物理约束。

一、HDD 磁盘结构#

1.1 磁盘的物理组成#

graph TB subgraph HDD["HDD 磁盘物理结构"] PLATTER["盘片 Platter<br/>双面涂覆磁性材料"] HEAD["磁头 Read/Write Head<br/>每面一个"] ARM["磁臂 Actuator Arm<br/>所有磁头同步移动"] SPINDLE["主轴 Spindle<br/>5400/7200/15000 RPM"] TRACK["磁道 Track<br/>同心圆"] SECTOR["扇区 Sector<br/>512B 或 4KB"] CYLINDER["柱面 Cylinder<br/>所有盘面同一磁道"] end PLATTER --> TRACK --> SECTOR PLATTER --> CYLINDER HEAD --> ARM style PLATTER fill:#e8eaf6,stroke:#283593 style HEAD fill:#e3f2fd,stroke:#1565c0 style ARM fill:#bbdefb,stroke:#1565c0

一块典型的 HDD 由以下部分组成:

组件说明对性能的影响
盘片(Platter)双面涂覆磁性材料的铝合金/玻璃圆盘盘片数决定容量
磁头(Head)读写数据的电磁转换器每面一个磁头
磁臂(Actuator Arm)带动磁头移动的机械臂所有磁头同步移动
主轴(Spindle)带动盘片旋转RPM 决定旋转延迟
磁道(Track)盘面上的同心圆密度决定容量
扇区(Sector)磁道上的最小寻址单元512B 或 4KB
柱面(Cylinder)所有盘面同一位置的磁道集合柱面内切换无需寻道

1.2 HDD 访问延迟分解#

HDD 的访问延迟由三个部分组成:

Taccess=Tseek+Trotation+TtransferT_{access} = T_{seek} + T_{rotation} + T_{transfer}

延迟组成含义典型值计算方式
寻道延迟 TseekT_{seek}磁臂移动到目标磁道3–15 ms取决于磁头移动距离
旋转延迟 TrotationT_{rotation}等待目标扇区旋转到磁头下方2–6 ms12×60RPM×1000\frac{1}{2} \times \frac{60}{RPM} \times 1000
传输延迟 TtransferT_{transfer}数据从盘面读取/写入0.01–0.1 ms数据量 / 传输速率
# HDD 延迟计算示例
def hdd_access_time(rpm=7200, seek_distance="average", data_size=4096):
"""计算 HDD 访问延迟"""
# 寻道延迟
seek_times = {
"track_to_track": 0.5, # ms, 相邻磁道
"average": 4.16, # ms, 平均寻道
"full_stroke": 10.0, # ms, 全程寻道
}
t_seek = seek_times[seek_distance]
# 旋转延迟(平均半圈)
t_rotation = 0.5 * (60 / rpm) * 1000 # ms
# 传输延迟
transfer_rate = 200 # MB/s (外圈)
t_transfer = (data_size / (1024 * 1024)) / transfer_rate * 1000 # ms
total = t_seek + t_rotation + t_transfer
print(f"寻道延迟: {t_seek:.2f} ms")
print(f"旋转延迟: {t_rotation:.2f} ms")
print(f"传输延迟: {t_transfer:.4f} ms")
print(f"总延迟: {total:.2f} ms")
print(f"寻道占比: {t_seek/total*100:.1f}%")
print(f"旋转占比: {t_rotation/total*100:.1f}%")
hdd_access_time()
# 寻道延迟: 4.16 ms
# 旋转延迟: 4.17 ms
# 传输延迟: 0.0195 ms
# 总延迟: 8.35 ms
# 寻道占比: 49.8%
# 旋转占比: 49.9%
Important

HDD 访问延迟的 99.9% 来自机械运动(寻道 + 旋转),数据传输几乎可以忽略。这就是为什么 HDD 的顺序读写比随机读写快几百倍——顺序读写只需一次寻道,而随机读写每次都需要寻道+等待旋转。

1.3 HDD 的性能特征#

操作延迟吞吐量IOPS
顺序读~5 ms(首次)100–250 MB/s
顺序写~5 ms(首次)100–200 MB/s
随机读 4KB~10 ms~0.4 MB/s~100
随机写 4KB~10 ms~0.4 MB/s~100
# 使用 fio 测试 HDD 性能
# 顺序读
fio --name=seq-read --rw=read --bs=1M --size=1G --numjobs=1 --runtime=60
# 随机读
fio --name=rand-read --rw=randread --bs=4K --size=1G --numjobs=1 --runtime=60 \
--iodepth=1 --ioengine=libaio
# 结果示例(7200 RPM HDD):
# 顺序读: ~180 MB/s
# 随机读: ~0.4 MB/s (~100 IOPS)
# 差距: 450x

二、NAND 闪存结构#

2.1 SSD 的层次结构#

graph TB subgraph SSD["SSD 内部结构"] CTRL["控制器 Controller<br/>FTL / ECC / GC / WL"] DRAM_SSD["DRAM 缓存<br/>映射表缓存"] subgraph 闪存层次 CHIP["闪存芯片 Chip/Die"] PLANE["平面 Plane<br/>2 或 4 个/Die"] BLOCK["块 Block<br/>数百个/Plane<br/>256–1024 页/块"] PAGE["页 Page<br/>4KB/8KB/16KB<br/>读/写单位"] end CTRL --> CHIP --> PLANE --> BLOCK --> PAGE DRAM_SSD --> CTRL end style CTRL fill:#e3f2fd,stroke:#1565c0 style PAGE fill:#c8e6c9,stroke:#2e7d32 style BLOCK fill:#fff9c4,stroke:#f9a825

NAND 闪存有三个关键层次:

层次大小操作说明
页(Page)4KB / 8KB / 16KB读、写最小读写单位
块(Block)256–1024 页擦除最小擦除单位
平面(Plane)数百–数千块可并行操作
Important

NAND 闪存的核心约束:写前必须擦除,擦除以块为单位。这意味着如果你只想修改 1 字节,也必须:读取整个块 → 修改目标字节 → 擦除整个块 → 写回整个块。这就是”写入放大”的物理根源。

2.2 NAND 闪存的类型#

类型全称每单元比特数P/E 次数速度成本用途
SLCSingle-Level Cell1 bit~100,000最快最高企业级/军工
MLCMulti-Level Cell2 bits~3,000消费级 SSD
TLCTriple-Level Cell3 bits~1,000一般主流消费级
QLCQuad-Level Cell4 bits~100–300最低归档/大容量
# SSD 寿命计算
def ssd_endurance(capacity_tb=1, nand_type="TLC", dwpd=1):
"""计算 SSD 寿命"""
pe_cycles = {"SLC": 100000, "MLC": 3000, "TLC": 1000, "QLC": 200}
# 总写入量 = 容量 × P/E 次数
total_write_tb = capacity_tb * pe_cycles[nand_type]
# 每日写入量(DWPD = Drive Writes Per Day)
daily_write_tb = capacity_tb * dwpd
# 寿命(天)
lifetime_days = total_write_tb / daily_write_tb
lifetime_years = lifetime_days / 365
print(f"NAND 类型: {nand_type}")
print(f"容量: {capacity_tb} TB")
print(f"P/E 次数: {pe_cycles[nand_type]:,}")
print(f"总写入量: {total_write_tb:,.0f} TB")
print(f"每日写入: {daily_write_tb} TB (DWPD={dwpd})")
print(f"预计寿命: {lifetime_years:.1f} 年")
ssd_endurance(1, "TLC", 1)
# NAND 类型: TLC
# 容量: 1 TB
# P/E 次数: 1,000
# 总写入量: 1,000 TB
# 每日写入: 1 TB (DWPD=1)
# 预计寿命: 2.7 年

2.3 写入放大详解#

写入放大(Write Amplification, WA)是 SSD 最核心的性能问题:

WA=实际写入闪存的数据量主机请求写入的数据量WA = \frac{\text{实际写入闪存的数据量}}{\text{主机请求写入的数据量}}

graph LR subgraph 写入放大过程 HOST["主机写入<br/>4KB"] --> FTL["FTL 处理"] FTL --> READ["读取目标块<br/>2MB"] READ --> MODIFY["修改目标页<br/>4KB"] MODIFY --> ERASE["擦除目标块<br/>2MB"] ERASE -> WRITE["写回整个块<br/>2MB"] end WA["WA = 2MB / 4KB = 512x!"] style HOST fill:#c8e6c9,stroke:#2e7d32 style WA fill:#ffcdd2,stroke:#c62828

写入放大的来源:

来源说明WA 贡献
页对齐写入必须对齐到页大小1–2x
GC 搬迁垃圾回收时搬迁移数据1–3x
FTL 映射映射表更新1–1.5x
磨损均衡冷热数据搬移1–1.5x
Compaction数据库层的合并操作1–10x
总 WA2–20x
Warning

写入放大直接消耗 SSD 的 P/E 次数。一块 TLC SSD 标称 DWPD=1 可用 2.7 年,但如果工作负载以随机小写入为主(如 WAL、LSM Compaction),实际 WA 可能达到 10x 以上,寿命缩短到不足 1 年。生产环境中务必通过 nvme smart-log 监控 Percentage Used 字段——当该值接近 100% 时应立即更换盘,否则会触发只读保护导致业务中断。

三、FTL 闪存转换层#

3.1 FTL 的核心功能#

FTL(Flash Translation Layer)是 SSD 的”操作系统”,负责在逻辑块地址(LBA)和物理页地址(PPA)之间做映射:

// FTL 核心数据结构(简化)
struct FTL {
// 逻辑到物理的映射表
// LBA (Logical Block Address) → PPA (Physical Page Address)
ppa_t *mapping_table; // 通常存在 DRAM 中
// 块信息表
struct BlockInfo {
int valid_page_count; // 有效页数
int erase_count; // 擦除次数(磨损均衡)
block_state_t state; // FREE/IN_USE/GC
} *block_info;
// 垃圾回收器
struct GC {
int threshold; // 触发 GC 的空闲块阈值
gc_policy_t policy; // GC 策略(贪心/成本效益)
};
};
// 写入流程
void ftl_write(FTL *ftl, lba_t lba, void *data, int size) {
// 1. 分配新的物理页(Out-of-place Update)
ppa_t new_ppa = allocate_page(ftl);
// 2. 写入数据到新物理页
nand_write(new_ppa, data, size);
// 3. 更新映射表
ppa_t old_ppa = ftl->mapping_table[lba];
ftl->mapping_table[lba] = new_ppa;
// 4. 标记旧页为无效
mark_page_invalid(ftl, old_ppa);
// 5. 检查是否需要 GC
if (free_blocks(ftl) < ftl->gc.threshold) {
garbage_collect(ftl);
}
}

3.2 地址映射策略#

映射策略粒度DRAM 占用性能适用场景
页级映射4KB 页高(1TB SSD ~1GB)最好随机 I/O 密集
块级映射块(~1MB)低(1TB SSD ~2MB)一般顺序 I/O 为主
混合映射热数据页级,冷数据块级中等较好通用场景

3.3 垃圾回收策略#

# SSD 垃圾回收策略
class GarbageCollector:
def select_victim_block(self, blocks):
"""选择回收哪个块"""
if self.policy == "greedy":
# 贪心策略:选择无效页最多的块
return max(blocks, key=lambda b: b.invalid_pages / b.total_pages)
elif self.policy == "cost_benefit":
# 成本效益策略:考虑块的使用频率和年龄
# cost_benefit = (1 - u) / (1 + u) * age
# u = 有效页比例, age = 距上次擦除的时间
return max(blocks, key=lambda b:
(1 - b.valid_ratio) / (1 + b.valid_ratio) * b.age)
elif self.policy == "d_choice":
# d-choice:随机选 d 个块,选最优的
candidates = random.sample(blocks, min(self.d, len(blocks)))
return max(candidates, key=lambda b: b.invalid_pages)
def gc_process(self, victim_block):
"""垃圾回收流程"""
# 1. 读取有效页
valid_pages = [p for p in victim_block.pages if p.is_valid]
# 2. 将有效页搬移到新块
for page in valid_pages:
new_ppa = self.allocate_page()
self.nand_write(new_ppa, page.data)
self.update_mapping(page.lba, new_ppa)
# 3. 擦除旧块
self.nand_erase(victim_block)
# 4. 更新统计
self.write_amplification += len(valid_pages)

3.4 磨损均衡#

磨损均衡(Wear Leveling)确保所有块的擦除次数均匀分布,避免某些块过早失效:

策略说明优点缺点
动态磨损均衡只在写入时选择擦除次数少的块简单冷数据块不参与
静态磨损均衡定期搬移冷数据,释放擦除次数少的块均匀额外写入开销
Tip

监控 SSD 健康状态的最佳工具是 smartctl -a /dev/nvme0n1,可以查看可用备用空间、已用寿命百分比、读写量等关键指标。结合 Prometheus 的 node_exporter + smartmon 脚本,可以实现 SSD 健康状态的自动告警。

四、SSD 接口与协议#

4.1 SATA vs NVMe#

特性SATA IIINVMe (PCIe 4.0)NVMe (PCIe 5.0)
带宽600 MB/s~7 GB/s~14 GB/s
队列深度32 (NCQ)65,53565,535
队列数165,53665,536
延迟~100 μs~10 μs~5 μs
IOPS(4K 随机读)~100K~1M~2M
协议开销高(AHCI)低(NVMe)低(NVMe)
# 查看 NVMe 设备信息
nvme list
# 查看 NVMe 健康状态
nvme smart-log /dev/nvme0
# 输出示例:
# Critical Warning : 0
# Temperature : 38°C
# Available Spare : 100%
# Percentage Used : 2% # 已用寿命百分比
# Data Units Read : 12,345,678
# Data Units Written : 23,456,789
# Host Read Commands : 456,789,012
# Host Write Commands : 345,678,901
# Controller Busy Time : 1,234 hours
# Power Cycles : 567
# Power On Hours : 8,760
# Unsafe Shutdowns : 12
# Media and Data Integrity Errors : 0

4.2 NVMe 多队列架构#

graph LR subgraph 主机侧 APP1["应用 1"] --> SQ1["提交队列 SQ 1"] APP2["应用 2"] --> SQ2["提交队列 SQ 2"] APP3["应用 3"] --> SQ3["提交队列 SQ 3"] end subgraph NVMe控制器 SQ1 --> PROC["控制器处理"] SQ2 --> PROC SQ3 --> PROC PROC --> CQ1["完成队列 CQ 1"] PROC --> CQ2["完成队列 CQ 2"] end CQ1 --> MSI["MSI-X 中断"] CQ2 --> MSI style PROC fill:#e3f2fd,stroke:#1565c0 style SQ1 fill:#c8e6c9,stroke:#2e7d32 style SQ2 fill:#c8e6c9,stroke:#2e7d32 style SQ3 fill:#c8e6c9,stroke:#2e7d32

NVMe 的多队列架构消除了 SATA/AHCI 的单队列瓶颈:

  • 提交队列(SQ):主机向控制器提交 I/O 命令
  • 完成队列(CQ):控制器向主机通知 I/O 完成
  • MSI-X 中断:多个中断向量,避免中断瓶颈

五、TRIM 与存储管理#

5.1 TRIM 命令#

TRIM 告诉 SSD 哪些逻辑块不再被使用,让 FTL 可以提前回收:

# 手动执行 TRIM
fstrim -v /mnt/ssd
# 查看是否支持 TRIM
lsblk -D
# DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
# 0 512B 2G 0
# 持续 TRIM(挂载选项)
mount -o discard /dev/nvme0n1p1 /mnt/ssd
# 注意:持续 TRIM 可能影响性能,建议使用定期 fstrim
# 在 /etc/fstab 中配置
# /dev/nvme0n1p1 /mnt/ssd ext4 defaults,noatime,discard 0 2

5.2 TRIM 对性能的影响#

场景无 TRIM有 TRIM
全新 SSD性能最优性能最优
使用 50% 后GC 开销增大,性能下降GC 高效,性能稳定
使用 90% 后严重性能下降(50%+)性能基本稳定
删除大量文件后FTL 不知道块已释放FTL 立即回收

六、HDD vs SSD 对比#

6.1 全面对比#

维度HDDSSD
随机读 IOPS~100~500K–2M
随机写 IOPS~100~100K–1M
顺序读吞吐100–250 MB/s3–14 GB/s
延迟5–15 ms10–100 μs
每 GB 成本~$0.03~$0.3–0.5
功耗5–10W1–5W
噪音
抗震
写入放大2–20x
寿命限制无(机械磨损)有(P/E 次数)
数据恢复较容易极困难

6.2 数据库场景的存储选择#

# 存储选择决策树
def choose_storage(workload):
if workload.type == "OLTP":
if workload.iops_requirement > 10000:
return "NVMe SSD (必选)"
elif workload.iops_requirement > 1000:
return "SATA SSD (推荐) 或 NVMe SSD"
else:
return "SATA SSD (够用)"
elif workload.type == "OLAP":
if workload.data_size > "10TB":
return "HDD + SSD 缓存 (成本优先)"
else:
return "NVMe SSD (性能优先)"
elif workload.type == "Archive":
return "HDD 或对象存储 (成本优先)"
elif workload.type == "Log":
return "NVMe SSD (低延迟写入)"

七、实战:观察磁盘与 SSD#

7.1 识别存储设备类型#

# 查看块设备信息
lsblk -o NAME,SIZE,TYPE,ROTA,TRAN,MODEL
# ROTA=0 → SSD, ROTA=1 → HDD
# 查看详细设备信息
smartctl -a /dev/nvme0n1
smartctl -a /dev/sda
# 查看 SSD 寿命
nvme smart-log /dev/nvme0n1 | grep "Percentage Used"

7.2 I/O 性能测试#

# 4K 随机读测试(SSD)
fio --name=rand-read-4k --rw=randread --bs=4k \
--size=10G --numjobs=4 --iodepth=32 \
--ioengine=libaio --direct=1 --runtime=60
# 4K 随机写测试(SSD)
fio --name=rand-write-4k --rw=randwrite --bs=4k \
--size=10G --numjobs=4 --iodepth=32 \
--ioengine=libaio --direct=1 --runtime=60
# 顺序读写测试
fio --name=seq-read --rw=read --bs=128k \
--size=10G --numjobs=1 --ioengine=libaio --direct=1
# 混合读写测试(70% 读 / 30% 写)
fio --name=mixed --rw=randrw --rwmixread=70 --bs=4k \
--size=10G --numjobs=4 --iodepth=32 \
--ioengine=libaio --direct=1 --runtime=60

7.3 观察写入放大#

# 查看 SSD 写入统计
nvme smart-log /dev/nvme0n1 | grep -E "Data Units|Host"
# 计算 WA
# WA = Data Units Written / Host Write Commands * (512 / 页大小)
# 例如:
# Data Units Written: 1,000,000 (× 512B = 512GB)
# Host Write Commands: 50,000,000 × 4KB = 200GB
# WA = 512GB / 200GB = 2.56x

7.4 MySQL 在不同存储上的表现#

-- 在 HDD 和 SSD 上分别执行
-- 插入 10 万条记录
INSERT INTO storage_test (data, payload)
SELECT CONCAT('record_', n), REPEAT('x', 1000)
FROM (
WITH RECURSIVE nums AS (
SELECT 1 AS n UNION ALL SELECT n+1 FROM nums WHERE n < 100000
) SELECT n FROM nums
) t;
-- 随机点查
SELECT * FROM storage_test WHERE id = 50000;
-- 范围扫描
SELECT COUNT(*) FROM storage_test WHERE id BETWEEN 40000 AND 60000;
操作HDDSATA SSDNVMe SSD
插入 10 万行~120s~15s~8s
随机点查~10ms~0.5ms~0.1ms
范围扫描 2 万行~500ms~50ms~20ms

八、总结#

主题核心要点关键词
HDD机械运动决定 10ms 级延迟,寻道+旋转占 99.9%,顺序 I/O 比随机快数百倍寻道, 旋转延迟
NAND 闪存页级读写、块级擦除约束导致写入放大,P/E 次数限制 SSD 寿命擦除块, P/E
FTL逻辑到物理映射、垃圾回收、磨损均衡是 SSD 核心机制GC, 磨损均衡
NVMe多队列架构消除 SATA 单队列瓶颈,延迟降低 10 倍多队列, 低延迟
选型OLTP 选 NVMe SSD,OLAP 大数据量用 HDD+SSD 缓存,归档用 HDD/对象存储场景选型

支持与分享

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

磁盘与 SSD 物理结构
https://blog.souloss.com/posts/storage/storage-disk-and-ssd/
作者
Souloss
发布于
2025-10-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时