在上一篇延迟分析中,我们把存储 I/O 路径拆成了七层,逐层度量延迟。但那个模型有一个隐含假设——块设备就在本机。把存储搬到云上之后,块设备层下面多了一整套分布式系统:网络传输、多副本复制、远端 SSD 集群、元数据服务。一次 4 KB 随机读在本地 NVMe SSD 上大约 70-120 微秒,但在云盘上通常要 100-200 微秒(EBS gp3)甚至更高。延迟多出来的部分去了哪里?
这个问题不只是”多了网络延迟”这么简单。云块存储是一个完整的分布式存储系统,涉及数据分片、三副本写入、日志结构合并、后台垃圾回收、跨可用区复制等机制,每一层都会引入延迟和抖动。理解这些机制,才能解释为什么云盘的 IOPS 有上限、为什么延迟有基线、为什么突发性能会耗尽、为什么 fio 测出来的数字和官方标称不一致。
本文从分布式块存储的通用架构开始,分析 AWS EBS 和阿里云 ESSD 两个主流实现的公开架构细节,然后拆解云盘性能规格的含义,给出 fio 性能测试的正确方法,最后讨论云盘选型和成本优化策略。
说明: 云厂商的内部架构并不完全公开。本文的架构分析基于 AWS re:Invent 演讲、AWS 官方博客、阿里云存储团队的公开论文和技术博客、以及 USENIX/FAST 等学术会议的相关论文。涉及推测或综合判断的部分会明确标注。
一、分布式块存储通用架构
1.1 为什么不能直接用本地盘
本地盘(Instance Store / 本地 NVMe SSD)的问题很明确:
- 数据和实例绑定。 实例停止或迁移后数据丢失。
- 容量受限于物理机。 单台物理机的 NVMe 插槽有限,不能灵活扩容。
- 没有持久化保障。 硬件故障意味着数据丢失,没有副本。
云块存储的核心目标就是解耦计算和存储:计算节点(EC2 / ECS)通过网络访问远端的存储集群,存储集群负责数据的持久化、冗余和弹性扩容。代价是每次 I/O 都要经过网络,延迟不可避免地增加。
1.2 通用架构三层模型
几乎所有云厂商的块存储系统都遵循类似的三层架构:
graph TB
subgraph 计算节点
A[虚拟机 / 容器] --> B[块设备前端<br/>virtio-blk / NVMe-oF]
end
subgraph 存储接入层
B -->|RDMA / TCP| C[BlockClient<br/>路由 + 分片]
end
subgraph 存储集群
C --> D1[存储节点 1<br/>ChunkServer]
C --> D2[存储节点 2<br/>ChunkServer]
C --> D3[存储节点 3<br/>ChunkServer]
end
subgraph 控制面
E[元数据服务<br/>Partition Map] -.-> C
E -.-> D1
E -.-> D2
E -.-> D3
end
这张图展示了分布式块存储的四个核心组件:
块设备前端(Block Frontend)。 运行在计算节点上,负责把虚拟机的块 I/O 请求转换成网络请求。早期实现用 virtio-blk 协议,现在高性能场景普遍用 NVMe over Fabrics(NVMe-oF)或自研协议。前端还负责 I/O 的分片——把一个大 I/O 按分片边界拆成多个小请求,分别发往不同的存储节点。
存储接入层(Block Client / Proxy)。 有些架构把分片逻辑放在独立的代理节点上(如早期的 Ceph RBD 通过 RADOS Gateway),有些直接嵌入计算节点的内核或用户态驱动里。接入层持有分片映射表(Partition Map),知道每个数据分片存在哪几个存储节点上。
存储节点(Chunk Server / Storage Server)。 实际存储数据的节点,每个节点管理多块本地 SSD。存储节点内部通常采用日志结构写入(Log-Structured Write)——所有写入先追加到日志,后台再合并到数据区。这样做的好处是把随机写转为顺序写,充分利用 SSD 的顺序写带宽。
元数据服务(Metadata Service)。 维护卷到分片、分片到存储节点的映射关系,处理卷的创建、删除、快照、扩容等控制面操作。元数据服务本身通常用 Paxos 或 Raft 做高可用。数据面(读写 I/O)不经过元数据服务,只有在分片迁移、故障切换等场景下才需要更新映射。
1.3 三副本写入流程
云块存储的持久性保障通常依赖三副本(3-way Replication)。以一次 4 KB 写入为例:
sequenceDiagram
participant VM as 虚拟机
participant FE as 块设备前端
participant P as 主副本
participant S1 as 从副本 1
participant S2 as 从副本 2
VM->>FE: write(4KB)
FE->>P: 写请求(分片 ID + 偏移 + 数据)
P->>P: 写入本地日志
par 并行复制
P->>S1: 复制数据
P->>S2: 复制数据
end
S1-->>P: ACK
S2-->>P: ACK
P-->>FE: 写入完成
FE-->>VM: write 返回
关键路径上的延迟组成:
- 计算节点到主副本的网络延迟。 在同一可用区(AZ)内,RDMA 网络的单程延迟大约 5-10 微秒,TCP 网络大约 20-50 微秒。
- 主副本本地日志写入。 追加写入 NVMe SSD 日志区,延迟大约 10-20 微秒。
- 主副本到从副本的网络延迟。 并行复制,取两个从副本中较慢的那个。同 AZ 内 5-50 微秒。
- 从副本本地日志写入。 同样追加写入日志。
- ACK 返回。 网络传输延迟。
整条链路下来,一次写入的典型延迟在 100-300 微秒,这就解释了为什么云盘的写延迟是本地 NVMe 的 3-10 倍。
1.4 读取路径分析
读取路径比写入路径更复杂,因为数据可能分布在多个位置:
- BlockClient 接收读请求。 根据分片映射表,定位到存储该偏移量数据的存储节点。读请求只需要访问一个副本(通常是主副本),不需要多数派读。
- 存储节点查找数据。 存储节点内部需要确定数据在本地 SSD 的哪个位置。如果使用日志结构存储引擎,数据可能分布在内存中的 MemTable、磁盘上的多层 SSTable 中,需要逐层查找。
- 读取 SSD 数据。 找到数据位置后,从本地 NVMe SSD 读取。如果数据在 SSD 的缓存中(控制器 DRAM 缓存或 Host Memory Buffer),延迟约 10-30 微秒;如果需要读取 NAND 闪存,延迟约 70-120 微秒。
- 通过网络返回数据。 数据通过 RDMA 或 TCP 返回给计算节点。
读取的一个关键问题是缓存分层:
- 存储节点的内存缓存。 热数据缓存在存储节点的 DRAM 中,命中时读延迟最低。
- SSD 控制器缓存。 SSD 控制器内部有 DRAM 缓存(通常几百 MB 到几 GB),缓存 FTL 映射表和热数据。
- NAND 闪存。 冷数据需要直接从 NAND 读取,延迟最高。
对于读密集型工作负载(如 OLTP 数据库的点查询),存储节点的缓存命中率直接决定了读延迟。缓存未命中率每上升 1%,平均延迟就可能增加几十微秒。这也解释了为什么云盘在冷启动后(例如刚从快照恢复)的读延迟远高于稳态——缓存还没有被预热。
1.5 日志结构写入与后台合并
存储节点内部通常采用日志结构合并树(LSM-Tree,Log-Structured Merge Tree)或类似的日志结构存储引擎。写入流程:
- 前台写入(Foreground Write)。 数据先写入内存中的 MemTable,同时追加到磁盘上的 WAL(Write-Ahead Log,预写日志)。MemTable 满后冻结,变成不可变的 Immutable MemTable。
- 后台 Flush。 后台线程把 Immutable MemTable 刷到磁盘,生成 SSTable 文件。
- 后台 Compaction。 多个 SSTable 文件积累后,后台线程做合并排序(Compaction),减少文件数量和空间放大。
这个设计对延迟的影响:
- 正常情况下写延迟低且稳定。 前台写入只涉及内存操作和 WAL 追加写,延迟在 10-30 微秒。
- Compaction 期间延迟可能抖动。 Compaction 消耗 SSD 带宽和 CPU,和前台 I/O 竞争资源,可能导致 P99 延迟上升。这是云盘延迟抖动的主要内部原因之一。
- 读操作可能需要查找多层。 如果数据分布在多个 SSTable 中,读操作需要逐层查找,可能涉及多次 SSD 读取。Bloom Filter 可以跳过不包含目标 key 的 SSTable,但不能完全消除多层查找的开销。
为什么选择日志结构而不是原地更新(In-place Update)?原因有三:第一,追加写对 SSD 友好——SSD 的随机写性能远低于顺序写性能,而且随机写加速 SSD 磨损和触发 GC。第二,追加写天然支持快照——保留历史版本只需要不删除旧数据。第三,追加写的并发控制更简单——多个写入之间不需要加锁保护同一个数据块。代价是读放大(Read Amplification)和空间放大(Space Amplification),需要靠 Compaction 来控制。
1.6 数据分片与负载均衡
一个云盘卷的数据并不存在单个存储节点上——它被切分成固定大小的分片(Chunk),典型大小在 64 MB 到 256 MB 之间,分散到多个存储节点上。分片的好处:
- 并行 I/O。 大 I/O 请求可以同时发往多个存储节点,聚合吞吐。
- 故障隔离。 单个存储节点故障只影响部分分片,其余分片的副本仍然可用。
- 热点打散。 把数据分散到多个节点,避免单节点成为性能瓶颈。
但分片也引入了新的问题:
- 小 I/O 的路由开销。 4 KB 的随机读只需要访问一个分片,但 BlockClient 需要查找分片映射表确定目标节点——这个查找本身有几微秒的开销。
- 跨分片 I/O 的原子性。 如果一次写入跨越了两个分片的边界,需要保证两个分片要么都写成功,要么都不写。这通常通过两阶段提交(2PC)或单边保证(写入方确保两个分片分别写入后再返回完成)来实现。
- 分片迁移的影响。 当存储集群需要做负载均衡或节点下线时,分片会在节点间迁移。迁移期间该分片的 I/O 延迟可能上升,因为请求需要被重定向到新节点。
1.7 云盘延迟比本地盘高的完整原因
把上面的分析汇总,云盘相比本地盘多出的延迟包括:
| 延迟来源 | 典型增量 | 说明 |
|---|---|---|
| 虚拟化层(virtio / vhost) | 5-20 微秒 | 虚拟机到宿主机的 I/O 转换 |
| 计算节点到存储节点网络 | 10-100 微秒 | 取决于 RDMA/TCP、网络拥塞 |
| 三副本复制 | 20-100 微秒 | 并行复制取最慢副本 |
| 存储引擎日志写入 | 10-30 微秒 | WAL 追加写 |
| 存储引擎读取放大 | 0-50 微秒 | LSM 多层查找 |
| 后台任务干扰 | 0-数毫秒 | Compaction、GC、副本修复 |
总延迟大约是本地 NVMe SSD 的 2-5 倍。使用 RDMA 网络和 NVMe-oF 协议的新一代云盘(如 AWS io2 Block Express、阿里云 ESSD PL3)能把延迟控制在 100-200 微秒,接近但仍高于本地 NVMe 的 70-120 微秒。
1.8 故障处理与数据修复
分布式块存储的一个核心设计目标是容忍硬件故障。故障处理流程直接影响性能:
存储节点故障。 当一个存储节点宕机或其上的 SSD 损坏时,该节点上的分片副本变得不可用。对于三副本系统,剩余两个副本仍然可以正常服务读写。系统在检测到故障后(通常通过心跳超时,几秒到几十秒),开始从存活的副本复制数据到新的存储节点,重建第三个副本。
数据修复期间的性能影响。 副本重建是带宽密集型操作——需要从存活副本读取全部数据并写入新节点。这个过程会消耗存储节点的 SSD 带宽和网络带宽,和前台 I/O 竞争资源。云厂商通常会对修复流量做限速,控制对前台 I/O 的影响,但延迟抖动仍然不可避免。
修复速度与风险窗口。 副本修复完成前,系统处于降级状态(两副本或一副本)。如果在修复完成前又有另一个副本故障,就可能导致数据丢失。修复速度取决于分片大小和可用带宽——一个 64 MB 的分片在 1 Gbps 的修复带宽下需要约 0.5 秒,但一个存储节点上可能有数万个分片,全部修复可能需要几分钟到几小时。
脑裂与一致性。 网络分区可能导致主副本和从副本之间失去联系。系统需要通过 epoch(纪元号)、lease(租约)等机制防止脑裂——确保在任意时刻只有一个主副本接受写入。分区恢复后,需要进行日志回放和数据对账,确保各副本一致。这些机制在正常运行时不影响性能,但在网络抖动或分区边缘场景下可能导致短暂的写入暂停(几百毫秒到几秒)。
这里有一个容易被忽略的问题:云盘的持久性数字(如 99.999%)是年化统计——它不保证任何单次故障中数据不丢失,只保证在一年的时间窗口内丢失数据的概率极低。对于真正不能丢失的数据,仍然需要应用层的跨 AZ 或跨 Region 复制。
二、AWS EBS 架构分析
2.1 EBS 的演进历史
AWS EBS 经历了多次架构重构,每次重构都显著改变了性能特征:
- 第一代(2008-2012)。 基于商用硬件和软件 RAID,存储节点使用 HDD。性能差、延迟高、抖动大。
- 第二代(2012-2017)。 引入 SSD
存储节点,架构逐步优化。推出
gp2(通用型 SSD)和io1(预置 IOPS SSD)。 - Nitro 时代(2017-至今)。
底层硬件全面迁移到 Nitro 架构——用自研的 Nitro Card(基于
ASIC 的网络和存储卸载卡)替代软件虚拟化层。推出
gp3、io2、io2 Block Express。
Nitro 架构对 EBS 性能的改善主要体现在两方面:一是把 EBS I/O 从宿主机 CPU 卸载到 Nitro Card 上处理,消除了 hypervisor 的 CPU 开销;二是用 Nitro Card 上的硬件加速器处理 NVMe 协议、加密、校验和,降低了每次 I/O 的固定开销。
2.2 EBS 数据面架构
根据 AWS 在 re:Invent 2021(“Deep dive on Amazon EBS”, STG302)和 2023(“Understanding Amazon EBS architecture”, STG331)中公开的信息,EBS 的数据面架构可以概括为:
计算节点侧: - 每个 EC2 实例通过 Nitro Card 访问 EBS 卷。Nitro Card 对虚拟机暴露 NVMe 设备接口,虚拟机内的操作系统使用标准 NVMe 驱动。 - Nitro Card 负责 NVMe 命令解析、数据加密(AES-256)、请求路由。I/O 请求通过 AWS 自研的网络协议(基于 SRD——Scalable Reliable Datagram)发往存储节点。
存储节点侧: - 每个 EBS 卷的数据被切分成固定大小的分片(Chunk),分散存储在多个存储节点上。 - 每个分片有两个副本(注意:EBS 是两副本,不是三副本),分布在同一可用区的不同故障域中。 - 存储节点使用本地 NVMe SSD 存储数据,内部采用日志结构写入。
写入路径: 写请求到达主副本后,主副本同时写入本地日志并将数据复制到从副本。两个副本都确认写入后,返回完成。这个两副本写入的设计是 EBS 延迟相对较低的原因之一——少一次副本复制意味着少一次网络往返。
读取路径: 读请求只从一个副本读取,不需要多副本一致性协议。读延迟主要取决于网络延迟加上存储节点的 SSD 读取延迟。
2.3 SRD 网络协议
SRD(Scalable Reliable Datagram)是 AWS 自研的网络传输协议,运行在 Nitro Card 的硬件上。与 TCP 的关键区别:
- 多路径传输。 SRD 在多条网络路径上同时发送数据包,利用数据中心网络的多路径拓扑(Clos 网络)。TCP 的单连接只走一条路径,容易受单链路拥塞影响。
- 乱序容忍。 SRD 在接收端做乱序重组,不像 TCP 那样因为单个丢包就阻塞整个流(Head-of-Line Blocking,队头阻塞)。
- 硬件卸载。 协议栈在 Nitro Card 上硬件实现,不占用宿主机 CPU。
- 拥塞控制。 SRD 使用自研的拥塞控制算法,针对数据中心内部的低延迟网络优化。与 TCP 的通用拥塞控制不同,SRD 能更快地检测和响应网络拥塞,减少尾延迟。
SRD 对 EBS 性能的影响是显著的——通过多路径传输减少了网络延迟的方差,使得 P99 延迟更加稳定。AWS 公布的数据显示,io2 Block Express 的 P99 读延迟可以做到亚毫秒级。
这里有一个工程上的启示:传统的存储网络方案(iSCSI over TCP、FC over Ethernet)都面临 TCP 队头阻塞和单路径传输的问题。SRD 和 RDMA 的普及意味着,分布式存储系统的网络层不再是”把两端连起来就行”,而是和存储引擎一样需要精心设计。
2.4 EBS 限速机制
EBS 对每个卷实施令牌桶(Token Bucket)限速。IOPS 和吞吐分别有独立的令牌桶——IOPS 令牌桶以配置的 IOPS 速率补充令牌,吞吐令牌桶以配置的吞吐速率补充令牌。一次 I/O 需要同时消耗两个桶的令牌。
当令牌耗尽时,EBS 会对 I/O
进行节流(Throttling)——排队等待令牌补充。在 CloudWatch
中,VolumeQueueLength(队列长度)指标上升是节流的直接信号。当
VolumeQueueLength 持续大于 1 时,说明 I/O
请求在排队等待,延迟会随之上升。
一个容易被忽略的细节:EBS 的限速是按卷计算的,但实例级的 EBS 带宽限制是按实例计算的。如果一个实例挂载了多个卷,所有卷共享实例级的 EBS 带宽。三个 gp3 卷各配置 3,000 IOPS(共 9,000 IOPS),但实例的 EBS IOPS 上限只有 6,000——这时会在实例级触发限流,而不是在单个卷上。排查性能问题时,需要同时检查卷级和实例级的限流指标。
2.5 gp3、io2、io2 Block Express 对比
| 维度 | gp3 | io2 | io2 Block Express |
|---|---|---|---|
| 最大 IOPS | 16,000 | 64,000 | 256,000 |
| 最大吞吐 | 1,000 MB/s | 1,000 MB/s | 4,000 MB/s |
| 最大容量 | 16 TiB | 16 TiB | 64 TiB |
| 基线 IOPS | 3,000(免费) | 按容量(500 IOPS/GiB) | 按容量(500 IOPS/GiB) |
| 延迟(官方标称) | 单位数毫秒 | 单位数毫秒 | 亚毫秒(sub-millisecond) |
| 持久性 | 99.8%-99.9% | 99.999% | 99.999% |
| 多挂载(Multi-Attach) | 不支持 | 支持 | 支持 |
| Nitro 要求 | 推荐 | 推荐 | 必须(R5b/R6i 等) |
几个关键区别需要解释:
gp3 的 IOPS 和吞吐独立配置。 gp3 允许独立设置 IOPS(最高 16,000)和吞吐(最高 1,000 MB/s),互不绑定。基线 3,000 IOPS 和 125 MB/s 吞吐免费包含在存储费用中。这个设计让用户可以按需购买性能,避免为了 IOPS 购买不需要的容量。
io2 Block Express 的架构差异。 io2 Block Express 不是 io2 的简单升级——它使用了完全不同的数据通路。普通 io2 的 I/O 通过 Nitro Card 的 EBS 控制器处理,io2 Block Express 使用 NVMe over Fabrics 协议直接和存储集群通信,跳过了中间的协议转换层。这是它能实现 256,000 IOPS 和亚毫秒延迟的关键原因。
持久性差异。 gp3 的年化持久性为 99.8%-99.9%(AFR 约 0.1%-0.2%),io2 的持久性为 99.999%(AFR 约 0.001%)。io2 通过更多的冗余和更积极的后台数据校验实现更高持久性。
2.6 EBS 突发性能机制
gp3 的 3,000 基线 IOPS 不消耗突发额度——这是 gp3 和 gp2 的关键区别。gp2 使用 I/O 信用(I/O Credits)机制:卷按容量积攒信用(每 GiB 每秒 3 IOPS 的速率),突发时消耗信用,信用耗尽后回落到基线。小容量 gp2 卷(如 100 GiB,基线仅 300 IOPS)在持续负载下很快耗尽信用,性能骤降。
gp3 取消了信用机制,基线 3,000 IOPS 始终可用。如果配置了更高的 IOPS(如 10,000),这个性能也是持续可用的,不依赖信用。这个改变让 gp3 的性能更可预测。
三、阿里云 ESSD 架构分析
3.1 盘古存储系统
阿里云的块存储服务(ESSD)底层基于盘古(Pangu)分布式存储系统。盘古是阿里云的统一存储底座,同时服务块存储、对象存储(OSS)和文件存储(NAS)。盘古的架构在 USENIX ATC 2023 论文 “More Than Capacity: Performance-oriented Evolution of Pangu in Alibaba” 和 FAST 2023 论文 “What’s the Story in EBS Glory: Evolutions and Lessons in Building Cloud Block Store” 中有详细描述。
盘古的核心组件:
- Pangu Master。 元数据管理服务,负责文件(块设备卷在盘古内部被抽象为文件)到 Chunk 的映射、Chunk 到存储节点的映射、副本调度。使用 Raft 做高可用。
- ChunkServer。 存储节点进程,每个物理存储节点运行一个或多个 ChunkServer 实例。ChunkServer 管理本地 SSD 上的数据读写。
- BlockClient。 运行在计算节点上的客户端库,负责 I/O 路由、分片、重试。在 ESSD 架构中,BlockClient 嵌入到虚拟化层(基于自研的神龙架构)。
3.2 ESSD 的 I/O 路径
根据 FAST 2023 论文,ESSD 的 I/O 路径如下:
- 虚拟机发起 I/O。 通过 virtio-blk 或 NVMe 前端设备发送到宿主机。
- 神龙 MOC 卡处理。 阿里云的神龙(X-Dragon)架构使用 MOC(Multi-function On-chip)卡——类似 AWS Nitro Card 的硬件卸载卡——处理虚拟化和存储 I/O 转发。MOC 卡负责 NVMe 命令解析和网络传输。
- BlockClient 路由。 根据卷的分片映射表,将 I/O 路由到对应的 ChunkServer。
- ChunkServer 处理。 ChunkServer 接收写请求后,写入本地 Append-Only 日志(称为 SealedSegment 和 AppendPoint),同时将数据复制到两个从副本(ESSD 也是三副本)。
- 三副本确认。 三个副本都写入本地日志后,主副本返回完成。
3.3 盘古 2.0 的性能优化
FAST 2023 论文描述了盘古 2.0 相对于 1.0 的几个关键性能优化:
用户态存储引擎(UESSD,User-space ESSD)。 早期的 ChunkServer 使用内核态文件系统(ext4)管理本地 SSD 上的数据。盘古 2.0 将存储引擎移到用户态,直接通过 SPDK(Storage Performance Development Kit)和用户态 NVMe 驱动访问 SSD,跳过了内核的 VFS、Page Cache 和块设备层。这一个改变就能减少 30-50 微秒的延迟(内核态 I/O 栈的开销)。
RDMA 网络。 PL2 和 PL3 使用 RDMA(Remote Direct Memory Access,远程直接内存访问)替代 TCP 网络。RDMA 绕过内核网络栈,CPU 不参与数据传输,单程网络延迟从 TCP 的 20-50 微秒降到 3-10 微秒。
Append-Only 写入优化。 盘古 2.0 的 ChunkServer 对每个 Chunk 维护一个 AppendPoint(追加点),所有写入都追加到 AppendPoint 后面。读取时,ChunkServer 维护一个索引(BlockMap),记录每个逻辑偏移对应的物理位置。这种设计把随机写变成顺序追加,充分利用 SSD 的顺序写带宽,同时避免了 SSD 内部的 GC 开销——因为数据总是追加的,覆盖写的空间回收由盘古自己的 GC 控制,而不是依赖 SSD 控制器的 GC。
Fusion Engine。 论文提到的 Fusion Engine 是盘古存储引擎的核心组件,负责将多个小写入合并成大的顺序写。例如,多个 4 KB 写入请求可以被合并成一个 64 KB 的顺序追加。这种合并减少了 SSD 的写放大,也提高了 SSD 的有效 IOPS——因为 SSD 的内部写放大(WAF)随着写入块大小的增加而降低。
3.4 ESSD 性能等级
ESSD 按性能等级分为四个规格:
| 维度 | ESSD PL0 | ESSD PL1 | ESSD PL2 | ESSD PL3 |
|---|---|---|---|---|
| 单盘最大 IOPS | 10,000 | 50,000 | 100,000 | 1,000,000 |
| 单盘最大吞吐 | 180 MB/s | 350 MB/s | 750 MB/s | 4,000 MB/s |
| 单路延迟 | 0.2-0.5 ms | 0.2-0.5 ms | 0.1-0.2 ms | 0.1 ms |
| 最小容量 | 40 GiB | 20 GiB | 461 GiB | 1,261 GiB |
| IOPS 计算公式 | min(10000, 1800 + 50 * 容量) | min(50000, 1800 + 50 * 容量) | min(100000, 1800 + 50 * 容量) | min(1000000, 1800 + 50 * 容量) |
| 适用场景 | 开发测试、小型数据库 | 中大型数据库、中间件 | 大型 OLTP、高性能应用 | 超大规模数据库、极致性能 |
几个要点:
IOPS 和容量绑定。 不同于 AWS gp3 的 IOPS 独立配置,ESSD 的 IOPS 和容量线性相关——每 GiB 提供 50 IOPS,基线 1,800 IOPS,上限取决于 PL 等级。想要更高 IOPS 必须购买更大容量,即使业务数据量用不了那么大。
PL3 依赖高端实例。 百万级 IOPS 需要实例本身有足够的网络带宽和 CPU 能力。只有 ecs.g7se、ecs.r7se 等增强型实例才能发挥 PL3 的全部性能。
延迟差异来自网络。 PL2/PL3 使用 25GE/100GE RDMA 网络,PL0/PL1 使用普通 TCP 网络。网络传输协议的差异是延迟差异的主要原因。
3.5 ESSD AutoPL
2023 年阿里云推出 ESSD AutoPL,支持按需突发——基线性能由容量决定,突发 IOPS 最高可达 100,000,突发吞吐最高可达 800 MB/s。突发性能按使用量计费。这个模式适合负载波动大但峰值持续时间短的场景(如电商大促、定时批处理),避免为峰值性能常年付费。
3.6 ESSD 与 EBS 的架构差异
| 维度 | AWS EBS | 阿里云 ESSD |
|---|---|---|
| 副本数 | 2 | 3 |
| 网络传输 | SRD(自研) | RDMA(PL2/PL3)/ TCP(PL0/PL1) |
| 硬件卸载 | Nitro Card | 神龙 MOC 卡 |
| IOPS 定价 | gp3 独立配置 | 与容量绑定 |
| 最高单盘 IOPS | 256,000(io2 BE) | 1,000,000(PL3) |
| 最大单盘容量 | 64 TiB(io2 BE) | 64 TiB |
| 持久性(最高) | 99.999%(io2) | 99.9999999%(9 个 9,官方标称) |
两副本和三副本的取舍:AWS 选择两副本,写延迟更低(少一次网络往返和本地写入),但需要更复杂的后台修复机制来保证持久性。阿里云选择三副本,写延迟稍高,但在单节点故障时仍有两个完整副本可用,数据修复的紧迫性更低。两种设计各有道理——AWS 更激进地优化延迟,阿里云更保守地保证可靠性。
四、云盘性能规格解读
4.1 IOPS 的真实含义
IOPS(Input/Output Operations Per Second,每秒输入输出操作数)看起来简单,但隐藏了很多细节:
I/O 大小的影响。 云厂商标称的 IOPS 通常是 4 KB 或 16 KB 块大小下的数字。增大 I/O 大小后,IOPS 通常会下降——因为实际瓶颈从 IOPS 变成了吞吐。一个 16,000 IOPS、1,000 MB/s 吞吐的 gp3 卷,在 4 KB 块大小下可以跑满 16,000 IOPS(总吞吐 62.5 MB/s),但在 256 KB 块大小下只能跑 4,000 IOPS(总吞吐 1,000 MB/s,受吞吐上限限制)。
随机 vs. 顺序。 标称 IOPS 通常是随机 I/O 的数字。顺序 I/O 因为可以被合并,实际的操作次数可能远少于应用发出的 I/O 次数,所以顺序场景下用吞吐而不是 IOPS 来衡量更有意义。
读写比例。 有些云盘规格对读写 IOPS 有不同的上限。例如 AWS io2 Block Express 的读写 IOPS 共享同一个上限,但读延迟和写延迟不同(读通常比写快,因为读只需访问一个副本)。
4.2 吞吐的约束
吞吐(Throughput)是另一个独立的性能维度。IOPS 和吞吐同时受限——取先触达上限的那个。
计算实际可达吞吐的公式:
实际吞吐 = min(最大吞吐, IOPS * I/O 大小)
例如 gp3 默认配置(3,000 IOPS、125 MB/s 吞吐): - 4 KB 块大小:3,000 * 4 KB = 12 MB/s(受 IOPS 限制,远未达到吞吐上限) - 256 KB 块大小:125 MB/s / 256 KB = 500 IOPS(受吞吐限制)
这说明:对于小 I/O 密集型负载(如数据库事务),IOPS 是瓶颈;对于大 I/O 顺序型负载(如日志写入、数据备份),吞吐是瓶颈。
4.3 延迟基线与抖动
云厂商标称的延迟通常是”单位数毫秒”或”亚毫秒”这样的模糊表述。实际延迟取决于多个因素:
空闲延迟(Idle Latency)。 卷没有负载时发送单个 I/O 的延迟,反映了网络传输和存储引擎的固有开销。EBS gp3 的空闲读延迟大约在 0.2-0.5 毫秒,io2 Block Express 大约在 0.1-0.2 毫秒。
负载延迟(Loaded Latency)。 随着并发 I/O 增加,排队延迟上升。当 IOPS 接近上限时,排队延迟可能占总延迟的 50% 以上。
延迟抖动。 云盘延迟不是恒定的,而是有波动。波动来源包括: - 存储节点后台 Compaction 和 GC - 网络拥塞(共享网络带宽的其他租户的干扰) - 副本修复(某个副本故障后的数据重建) - 热点分片(大量 I/O 集中在少数分片上)
P99 延迟通常是 P50 延迟的 3-10 倍。在做容量规划时,应该用 P99 而不是平均延迟来估算。
4.4 实例级限制
云盘的性能还受实例类型的限制。每种实例类型都有自己的 EBS 带宽和 IOPS 上限:
EC2 实例 EBS 性能上限示例(AWS):
┌──────────────────┬────────────┬──────────────┬──────────┐
│ 实例类型 │ EBS 带宽 │ EBS IOPS │ NVMe 队列│
├──────────────────┼────────────┼──────────────┼──────────┤
│ m6i.large │ 650 MB/s │ 3,600 │ 共享 │
│ m6i.xlarge │ 1,250 MB/s │ 6,000 │ 共享 │
│ m6i.4xlarge │ 2,500 MB/s │ 15,000 │ 专用 │
│ m6i.16xlarge │ 5,000 MB/s │ 40,000 │ 专用 │
│ r5b.24xlarge │ 10,000 MB/s│ 60,000 │ 专用 │
│ r6i.metal │ 5,000 MB/s │ 40,000 │ 专用 │
└──────────────────┴────────────┴──────────────┴──────────┘
注:以上数据引用自 AWS 官方文档(2024 年),实际可能随实例代次更新。
关键点:即使云盘配置了 16,000 IOPS,如果实例的 EBS IOPS 上限只有 3,600(如 m6i.large),实际只能跑到 3,600。这是新手最常见的困惑之一——买了高性能云盘却跑不出预期性能,原因是实例规格不够。
4.5 I/O 大小与性能的关系
云盘的 IOPS 和吞吐在不同 I/O 大小下的表现差异很大。以一个配置了 16,000 IOPS、1,000 MB/s 吞吐的 gp3 卷为例:
I/O 大小与性能约束关系(gp3: 16K IOPS / 1000 MB/s):
┌──────────┬────────────────┬────────────────┬──────────────┐
│ I/O 大小 │ IOPS 约束吞吐 │ 吞吐约束 IOPS │ 实际瓶颈 │
├──────────┼────────────────┼────────────────┼──────────────┤
│ 4 KB │ 62.5 MB/s │ 256,000 │ IOPS │
│ 8 KB │ 125 MB/s │ 128,000 │ IOPS │
│ 16 KB │ 250 MB/s │ 64,000 │ IOPS │
│ 64 KB │ 1,000 MB/s │ 16,000 │ 两者同时触顶 │
│ 128 KB │ 2,000 MB/s │ 8,000 │ 吞吐 │
│ 256 KB │ 4,000 MB/s │ 4,000 │ 吞吐 │
│ 1 MB │ 16,000 MB/s │ 1,000 │ 吞吐 │
└──────────┴────────────────┴────────────────┴──────────────┘
可以看到,64 KB 是这个配置下的平衡点——IOPS 和吞吐同时达到上限。I/O 大小小于 64 KB 时是 IOPS 受限,大于 64 KB 时是吞吐受限。
这对数据库设计有直接影响。MySQL InnoDB 的页大小默认是 16 KB,PostgreSQL 的页大小默认是 8 KB——这些都在 IOPS 受限区间。如果数据库的瓶颈是随机读写 IOPS,增加云盘的 IOPS 配置比增加吞吐配置更有效。反过来,Kafka 的日志写入使用大块顺序写,通常在吞吐受限区间,应该优先配置更高的吞吐。
4.6 性能监控指标
在实际运行中,需要持续监控以下指标来判断云盘是否成为瓶颈:
AWS CloudWatch 关键指标:
| 指标名 | 含义 | 告警阈值建议 |
|---|---|---|
VolumeReadOps /
VolumeWriteOps |
读写操作次数 | 接近配置 IOPS 的 80% |
VolumeReadBytes /
VolumeWriteBytes |
读写字节数 | 接近配置吞吐的 80% |
VolumeQueueLength |
I/O 队列长度 | 持续大于 1 表示排队 |
VolumeTotalReadTime /
VolumeTotalWriteTime |
读写总耗时 | 用于计算平均延迟 |
BurstBalance(gp2) |
突发余额百分比 | 低于 20% 预警 |
平均延迟的计算方式:
平均读延迟 = VolumeTotalReadTime / VolumeReadOps
平均写延迟 = VolumeTotalWriteTime / VolumeWriteOps
注意:CloudWatch
提供的是平均延迟,不提供百分位延迟。要获取 P99
延迟,需要在操作系统内部用 biolatency 或 fio
的延迟直方图来采集。
五、云盘性能测试方法
5.1 为什么直接跑 fio 经常得到错误结果
常见错误:
- 没有预热(Pre-conditioning)。 新创建的 EBS 卷或从快照恢复的卷,首次读取每个块时需要从 S3 或快照存储中拉取数据,延迟远高于正常值。必须先对全卷做一次顺序写或读来完成预热。
- I/O 深度不够。 云盘的 IOPS 上限需要足够的并发 I/O 才能达到。单线程同步 I/O(iodepth=1)只能在延迟的倒数处运转——如果延迟是 0.2 毫秒,单线程最多 5,000 IOPS。要跑满 16,000 IOPS,至少需要 iodepth=4(16000 * 0.00025 = 4)。
- 用了文件系统而不是裸设备。
文件系统引入额外的延迟和 IOPS
开销(元数据操作、日志提交)。性能测试应该直接测裸设备(
/dev/nvme1n1),除非目的就是测试文件系统上的性能。 - 测试时间太短。 短时间测试可能测到突发性能而不是持续性能。至少跑 5 分钟以上。
- 没有等待稳态(Steady State)。 开始测试后前几分钟的性能可能不稳定(缓存效应、存储引擎预热)。应该丢弃前几分钟的数据,只统计稳态数据。
5.2 I/O 深度的计算
队列深度(I/O Depth / Queue Depth)是性能测试中最关键的参数。计算所需队列深度的公式:
所需队列深度 = 目标 IOPS * 平均延迟(秒)
举几个例子:
| 目标 IOPS | 预估延迟 | 所需队列深度 | fio 配置建议 |
|---|---|---|---|
| 3,000(gp3 基线) | 0.3 ms | 0.9 ≈ 1 | iodepth=1, numjobs=1 |
| 16,000(gp3 最大) | 0.3 ms | 4.8 ≈ 8 | iodepth=8, numjobs=1 |
| 64,000(io2) | 0.2 ms | 12.8 ≈ 16 | iodepth=16, numjobs=2 |
| 256,000(io2 BE) | 0.15 ms | 38.4 ≈ 64 | iodepth=32, numjobs=4 |
实际测试中通常设置比计算值更高的队列深度(2-4
倍),确保不是队列深度不够导致跑不满。但队列深度也不能过高——过高的队列深度会人为增加排队延迟,使延迟测试结果失真。如果目的是测最低延迟而不是最大
IOPS,应该用 iodepth=1。
5.3 fio 测试命令
以下是测试 EBS gp3 卷的 fio 命令示例。假设测试设备为
/dev/nvme1n1(一个挂载到 EC2 实例上的 EBS
卷)。
第一步:预热——对全卷做顺序写
# 预热:顺序写满全卷,确保所有块都已初始化
# 注意:这会覆盖卷上的所有数据
fio --name=precondition \
--filename=/dev/nvme1n1 \
--ioengine=libaio \
--direct=1 \
--bs=1m \
--iodepth=64 \
--rw=write \
--numjobs=1 \
--size=100% \
--group_reporting第二步:4K 随机读 IOPS 测试
fio --name=randread-iops \
--filename=/dev/nvme1n1 \
--ioengine=libaio \
--direct=1 \
--bs=4k \
--iodepth=32 \
--rw=randread \
--numjobs=4 \
--runtime=300 \
--time_based \
--group_reporting \
--lat_percentiles=1 \
--percentile_list=50:90:95:99:99.9:99.99第三步:4K 随机写 IOPS 测试
fio --name=randwrite-iops \
--filename=/dev/nvme1n1 \
--ioengine=libaio \
--direct=1 \
--bs=4k \
--iodepth=32 \
--rw=randwrite \
--numjobs=4 \
--runtime=300 \
--time_based \
--group_reporting \
--lat_percentiles=1 \
--percentile_list=50:90:95:99:99.9:99.99第四步:顺序读吞吐测试
fio --name=seqread-throughput \
--filename=/dev/nvme1n1 \
--ioengine=libaio \
--direct=1 \
--bs=256k \
--iodepth=64 \
--rw=read \
--numjobs=4 \
--runtime=300 \
--time_based \
--group_reporting第五步:混合读写测试(70% 读 / 30% 写)
fio --name=mixed-rw \
--filename=/dev/nvme1n1 \
--ioengine=libaio \
--direct=1 \
--bs=4k \
--iodepth=32 \
--rw=randrw \
--rwmixread=70 \
--numjobs=4 \
--runtime=300 \
--time_based \
--group_reporting \
--lat_percentiles=15.4 fio 参数说明
| 参数 | 含义 | 为什么这样设 |
|---|---|---|
--ioengine=libaio |
使用 Linux 原生异步 I/O | 直接向内核提交异步请求,绕过 glibc 缓冲 |
--direct=1 |
绕过 Page Cache | 测试真实的块设备性能,不被缓存干扰 |
--bs=4k |
I/O 块大小 4 KB | 云厂商标称 IOPS 的基准块大小 |
--iodepth=32 |
队列深度 32 | 维持足够的并发 I/O 来饱和云盘性能 |
--numjobs=4 |
4 个并行任务 | 模拟多线程访问,总并发 = iodepth * numjobs = 128 |
--runtime=300 |
运行 300 秒 | 确保测到稳态性能而非突发 |
--time_based |
固定运行时间 | 即使文件读完也继续测试 |
--group_reporting |
合并报告 | 所有 job 的结果合并输出 |
--lat_percentiles=1 |
输出延迟百分位 | 查看 P50/P99/P99.9 延迟 |
5.5 fio 输出解读
以一个 gp3 卷(配置 3,000 IOPS、125 MB/s)的典型 4K 随机读输出为例(以下为典型输出格式,实际数值取决于具体环境):
randread-iops: (groupid=0, jobs=4): err= 0: pid=12345
read: IOPS=2998, BW=11.7MiB/s (12.3MB/s)(3517MiB/300001msec)
slat (usec): min=1, max=85, avg= 3.21, stdev= 1.52
clat (usec): min=62, max=15234, avg=378.52, stdev=125.67
lat (usec): min=65, max=15237, avg=381.73, stdev=125.89
clat percentiles (usec):
| 1.00th=[ 143], 5.00th=[ 192], 10.00th=[ 225],
| 20.00th=[ 269], 30.00th=[ 302], 40.00th=[ 334],
| 50.00th=[ 363], 60.00th=[ 392], 70.00th=[ 424],
| 80.00th=[ 465], 90.00th=[ 529], 95.00th=[ 594],
| 99.00th=[ 783], 99.50th=[ 898], 99.90th=[ 1434],
| 99.95th=[ 2024], 99.99th=[ 5800]
关键数字解读:
- IOPS=2998。 接近 3,000 基线,说明测试正确触达了性能上限。
- clat avg=378.52 usec。 平均完成延迟约 379 微秒(0.38 毫秒),符合 gp3 的”单位数毫秒”标称。
- P50=363 usec,P99=783 usec。 P99 约为 P50 的 2.2 倍,这个比例在云盘上算比较稳定的。
- P99.9=1434 usec(1.4 ms)。 千分之一的请求延迟超过 1.4 毫秒。
- P99.99=5800 usec(5.8 ms)。 万分之一的请求延迟接近 6 毫秒——这些极端值可能来自网络抖动或存储节点后台操作。
- slat(Submission Latency)非常小(3 微秒)。 说明 libaio 的提交开销可以忽略。
5.6 稳态验证
怎么判断测试是否达到了稳态?用 fio 的
--write_bw_log 和 --write_iops_log
参数输出时间序列数据:
fio --name=steady-state-check \
--filename=/dev/nvme1n1 \
--ioengine=libaio \
--direct=1 \
--bs=4k \
--iodepth=32 \
--rw=randread \
--numjobs=4 \
--runtime=600 \
--time_based \
--group_reporting \
--write_iops_log=gp3-iops \
--write_lat_log=gp3-lat \
--log_avg_msec=1000这会生成 gp3-iops_iops.1.log 和
gp3-lat_lat.1.log
文件,每秒一个数据点。用简单的脚本或者 gnuplot
画出时间序列图,观察 IOPS
和延迟是否在前几分钟后趋于平稳。如果 IOPS
持续下降或延迟持续上升,说明测试条件有问题(可能是存储节点在做后台任务,或者触发了性能限流)。
六、云盘选型与成本优化
6.1 选型决策树
云盘选型不应该只看性能参数——成本、持久性、弹性需求都要纳入考量。下面是一个简化的决策流程:
flowchart TD
A[确定工作负载特征] --> B{需要持久化?}
B -->|否| C[本地实例存储<br/>Instance Store]
B -->|是| D{延迟要求?}
D -->|亚毫秒| E{IOPS 需求?}
D -->|单位数毫秒可接受| F[gp3 / ESSD PL1]
E -->|大于 64K| G[io2 Block Express<br/>ESSD PL3]
E -->|小于 64K| H[io2 / ESSD PL2]
F --> I{容量需求?}
I -->|小于 1 TiB| J[gp3 基线 3000 IOPS<br/>性价比最高]
I -->|大于 1 TiB| K[gp3 按需加 IOPS<br/>或 st1 大容量]
G --> L[确认实例规格<br/>能支撑目标 IOPS]
H --> L
6.2 成本对比
以 AWS us-east-1 区域为例,不同 EBS 类型存储 1 TiB 数据、配置 10,000 IOPS 的月度成本估算(截至 2024 年,价格可能变化):
| 卷类型 | 存储费用 | IOPS 费用 | 吞吐费用 | 月度总计(估算) |
|---|---|---|---|---|
| gp3(1 TiB, 10K IOPS, 250 MB/s) | $81.92 | $45.50((10000-3000) * $0.0065) | $5.00((250-125) * $0.04) | 约 $132 |
| io2(1 TiB, 10K IOPS) | $125.00 | $650.00(10000 * $0.065) | 无单独费用 | 约 $775 |
| io2 Block Express(1 TiB, 10K IOPS) | $125.00 | $650.00 | 无单独费用 | 约 $775 |
| gp2(1 TiB,基线 3000 IOPS) | $102.40 | 无单独配置 | 无单独配置 | 约 $102 |
几个关键发现:
gp3 在大多数场景下性价比最高。 同样 10,000 IOPS,gp3 的成本约为 io2 的 1/6。除非业务对持久性有极高要求(99.999%)或需要 Multi-Attach,否则 gp3 通常是更好的选择。
io2 的 IOPS 单价远高于 gp3。 io2 每 IOPS 每月 $0.065,gp3 每 IOPS 每月 $0.0065(超过基线部分)。io2 的溢价主要买的是更高的持久性(99.999% vs. 99.8%-99.9%)和更低的延迟。
gp2 到 gp3 的迁移几乎总是值得的。 gp3 基线 3,000 IOPS + 125 MB/s 免费包含,而 gp2 需要 600 GiB 以上才能达到 3,000 IOPS 基线(1 GiB = 3 IOPS)。小容量卷从 gp2 迁移到 gp3 可以获得性能提升且成本不变或更低。
6.3 常见的成本浪费
为了 IOPS 购买不需要的容量(ESSD 场景)。 ESSD 的 IOPS 和容量绑定,想要 50,000 IOPS 至少需要 (50000-1800)/50 = 964 GiB 的 PL1 云盘。如果业务数据只有 100 GiB,剩余 864 GiB 是纯粹的成本浪费。这种情况下可以考虑使用 ESSD AutoPL,或者把数据分散到多块小容量 ESSD 上并行访问。
实例规格不匹配。 用 m6i.large(EBS 上限 3,600 IOPS)挂一块配置了 16,000 IOPS 的 gp3——多出来的 12,400 IOPS 完全浪费。先确认实例的 EBS 性能上限,再配置云盘 IOPS。
没有使用 gp3 的独立 IOPS/吞吐配置。 很多用户从 gp2 迁移到 gp3 后,没有额外配置 IOPS 和吞吐,停留在基线 3,000 IOPS / 125 MB/s。如果负载需要更高性能,增加 IOPS 和吞吐的费用远低于升级到 io2。
快照费用忽视。 EBS 快照按增量存储计费($0.05/GiB/月),但长期积累的快照总量可能远超卷本身的大小。定期清理不需要的快照是控制存储成本的基本操作。
6.4 成本优化清单
- 审计现有卷的使用率。 用 CloudWatch 的
VolumeReadOps、VolumeWriteOps指标检查每个卷的实际 IOPS 使用率。如果实际 IOPS 长期低于基线 3,000,说明不需要额外购买 IOPS。 - gp2 全面迁移 gp3。 AWS 支持在线变更卷类型(Elastic Volumes),无需停机。迁移后根据实际负载调整 IOPS 和吞吐配置。
- 实例和云盘性能对齐。 实例的 EBS 带宽/IOPS 上限应该大于等于所有挂载云盘的性能总和。反过来也成立——不要用过大的实例挂小规格云盘。
- 冷数据下沉。 访问频率低的数据(日志归档、历史备份)迁移到 st1(吞吐优化 HDD,$0.045/GiB/月)或 sc1(冷 HDD,$0.015/GiB/月)。
- 快照生命周期管理。 使用 AWS Data Lifecycle Manager(DLM)或阿里云快照策略自动管理快照保留周期,避免快照无限增长。
- 考虑 ESSD AutoPL 替代固定高规格。 如果负载的峰值持续时间短(每天不超过几小时),按需突发模式的费用可能低于常年购买高 PL 等级。
七、进阶话题
7.1 跨可用区复制与 Multi-Attach
跨可用区复制。 默认情况下,EBS 卷的副本在同一个可用区(AZ)内。如果整个 AZ 故障(虽然罕见但有先例——2012 年 US-East-1 AZ 级故障、2020 年 AP-Northeast-1 事件),卷数据不可访问。跨 AZ 的数据保护需要额外方案:EBS 快照(存储在 S3,跨 AZ 持久化)、应用层复制(如数据库主从)、或 AWS Outposts 的本地存储。
Multi-Attach。 io2 和 io2 Block Express 支持将同一个卷同时挂载到最多 16 个实例上(必须在同一 AZ)。Multi-Attach 不提供文件系统级的协调——需要应用自己实现分布式锁或使用集群文件系统(如 GFS2、OCFS2)。典型场景是共享存储集群(如 Oracle RAC)。
7.2 加密对性能的影响
EBS 默认支持透明加密(AES-256),加密/解密由 Nitro Card 硬件执行,不占用实例 CPU。AWS 官方声明加密对性能的影响”极小”(negligible)。实测中,加密卷和未加密卷的 IOPS 和延迟差异通常在 1-3% 以内——这在实际工作负载中基本不可感知。阿里云的 ESSD 同样支持透明加密,由 MOC 卡硬件处理。
我认为在 2024 年的云环境中,没有理由不开启存储加密。性能影响可以忽略,而加密带来的合规和安全收益是明确的。
7.3 快照与克隆的性能影响
从快照恢复的卷需要预热。 EBS 快照存储在 S3 上。从快照创建的卷,首次访问每个块时需要从 S3 拉取数据——这个延迟可能高达数毫秒到数十毫秒,远高于正常的块设备延迟。AWS 提供 Fast Snapshot Restore(FSR)功能,可以预初始化卷的所有块,消除首次访问的延迟惩罚。FSR 按启用的 AZ 和快照计费($0.75/快照/AZ/小时),成本不低,但对于延迟敏感的生产数据库恢复场景很有价值。
阿里云快照的类似问题。 从快照创建的 ESSD 同样存在首次读取延迟的问题。阿里云的解决方案是”快照极速可用”功能,原理类似——提前将数据从快照存储恢复到块存储集群。
7.4 NVMe over Fabrics 与未来趋势
NVMe over Fabrics(NVMe-oF)是云块存储架构演进的方向。NVMe-oF 把 NVMe 协议从 PCIe 总线扩展到网络——虚拟机内的 NVMe 驱动可以直接和远端存储节点通信,不需要中间的协议转换层(如 virtio-blk 到自研协议再到存储引擎)。
AWS io2 Block Express 已经使用了 NVMe-oF 的思路(虽然底层协议是 SRD 而非标准 NVMe-oF)。阿里云的 ESSD PL3 也在向 NVMe-oF 方向演进。
NVMe-oF 对性能的意义在于减少协议转换层数——每减少一层协议转换,就减少一次数据拷贝和延迟。长期来看,云盘的延迟有望进一步接近本地 NVMe SSD 的水平。但网络传输延迟和多副本复制的开销是物理限制,不可能完全消除。我认为云盘延迟的下限大约在 50-100 微秒——在 RDMA 网络 + 两副本 + 硬件卸载的极致优化下。
7.5 常见性能问题排查
在实际运维中,云盘相关的性能问题排查有一套固定的流程。以下是常见问题和对应的排查方法:
问题一:IOPS 跑不满标称值
排查顺序: 1. 检查实例级 EBS 性能上限(CloudWatch
EBSIOBalance% 或实例文档)。 2. 检查 fio 的
iodepth * numjobs 是否足够(参见 5.2 节的计算公式)。 3.
检查是否使用了 --direct=1——不用 direct I/O
时,小 I/O 可能被 Page Cache 合并,实际到达块设备的 IOPS
比预期少。 4.
检查卷是否已预热——未预热的卷首次读取块时会触发后端拉取,IOPS
远低于正常值。
问题二:延迟突然飙高
排查顺序: 1. 检查
VolumeQueueLength——如果队列长度突增,说明触发了限流。
2. 检查是否有突发
I/O(如数据库检查点、日志轮转)导致短时间内 IOPS
超过配置值。 3. 检查实例的网络带宽使用——EBS I/O
和网络流量共享实例的网络带宽(部分实例类型),网络拥塞会影响
EBS 延迟。 4. 检查 dmesg 中是否有 NVMe
超时或重试日志——这可能指示存储后端的临时故障。
问题三:性能周期性波动
可能的原因: 1. 存储后端的 Compaction 或 GC
是周期性任务,会占用 SSD 带宽,导致前台 I/O 延迟周期性上升。
2. 数据库的检查点间隔(如 MySQL 的
innodb_log_file_size 写满后触发检查点)导致 I/O
周期性突增。 3. cron
定时任务(备份、日志压缩)在固定时间点产生突发 I/O。
对于第一种情况,用户端无法直接控制存储后端的行为,但可以通过增大 IOPS 配置预留余量、或使用延迟更低的卷类型(如 io2 Block Express)来缓解。
八、结论与工程建议
8.1 核心结论
云盘延迟高于本地盘是架构决定的。 网络传输、多副本复制、存储引擎的日志结构写入和后台合并——这些是分布式块存储的固有开销,不是”优化不够”。理解这一点才能正确设定性能预期。
IOPS、吞吐、延迟是三个独立的约束维度。 云盘性能受三者中最先触达上限的那个限制。小 I/O 看 IOPS,大 I/O 看吞吐,延迟敏感型负载看 P99 延迟。选型时三个维度都要评估。
实例规格是隐藏的性能瓶颈。 云盘性能再高,也不能超过实例的 EBS 带宽和 IOPS 上限。性能调优要先确认瓶颈在实例侧还是云盘侧。
gp3 在绝大多数场景下是性价比最优选择。 除非有明确的亚毫秒延迟要求、99.999% 持久性要求、或超过 16,000 IOPS 的需求,否则从 gp3 开始,按需加量。
性能测试要用正确的方法。 预热、足够的 I/O 深度、裸设备测试、足够长的运行时间、稳态验证——任何一步缺失都会导致测试结果失真。
8.2 工程建议
- 新项目默认选 gp3。 基线 3,000 IOPS + 125 MB/s 免费,按需扩展 IOPS 和吞吐,成本可控。
- 数据库场景关注 P99 延迟。
数据库对延迟抖动敏感,用 fio 的百分位延迟输出和 CloudWatch
的
VolumeQueueLength指标评估延迟分布。 - 大规模部署做容量规划。 把实例的 EBS 性能上限、云盘的性能配置、实际工作负载特征三者对齐。用监控数据而不是直觉做决策。
- 定期审计存储成本。 检查未使用的卷、过期的快照、过度配置的 IOPS——存储成本是云账单中最容易被忽视但增长最快的部分。
参考资料
官方文档与演讲
- AWS. “Amazon EBS volume types.” AWS 官方文档,包含 gp3、io2、io2 Block Express 的详细性能规格。
- AWS re:Invent 2021, STG302. “Deep dive on Amazon EBS.” EBS 架构和 Nitro 集成的详细讲解。
- AWS re:Invent 2023, STG331. “Understanding Amazon EBS architecture.” EBS 数据面架构和 SRD 协议介绍。
- 阿里云. “ESSD 云盘.” 阿里云官方文档,包含 ESSD PL0-PL3 性能规格。
论文
- Li, Jianguo et al. “What’s the Story in EBS Glory: Evolutions and Lessons in Building Cloud Block Store.” FAST 2023. 阿里云 EBS(ESSD)架构演进的详细论文,包含盘古存储系统的设计和性能分析。
- Guo, Hao et al. “More Than Capacity: Performance-oriented Evolution of Pangu in Alibaba.” USENIX ATC 2023. 盘古分布式存储系统面向性能的架构演进。
- Arpaci-Dusseau, Remzi H. and Arpaci-Dusseau, Andrea C. “Operating Systems: Three Easy Pieces.” 第 39-42 章,SSD 和闪存存储的基础知识。
工具与测试
- fio 项目文档(https://fio.readthedocs.io)——fio 的完整参数说明和使用指南。
- AWS. “Benchmark EBS Volumes.” AWS 官方文档中的 EBS 性能测试指南,包含 fio 参数推荐。
上一篇:【存储工程】存储全链路延迟分析
下一篇:【存储工程】云对象存储内部架构
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【存储工程】存储基准测试方法论
深入剖析存储基准测试的方法论——fio 参数解析、filebench 工作负载定义、YCSB 数据库基准、测试陷阱规避与性能回归测试集成
【存储工程】存储性能建模:IOPS、吞吐与延迟
存储性能不是一个数字,而是 IOPS、吞吐量和延迟在特定工作负载下的函数关系。本文从排队论模型出发,用 fio 实测验证,覆盖从 HDD 到 NVMe SSD 的性能画像,最终落到容量规划和监控体系的工程实践。
【存储工程】HDD 机械硬盘:旋转时代的工程遗产
HDD 已经被 SSD 抢去了大部分聚光灯,但全球 90% 以上的数据仍然存储在旋转磁盘上。理解 HDD 的物理结构和性能特征,是理解整个存储栈设计决策的基础——从文件系统的块分配策略到数据库的 WAL 设计,几乎每一个存储优化都能追溯到'旋转延迟'和'寻道时间'这两个物理约束。
数据库内核实验索引
汇总本站数据库内核与存储引擎实验文章,重点覆盖从零实现 LSM-Tree 及其工程权衡。