<audio id="audio" title="17 | 存储篇：如何根据性能优缺点选择最合适的存储系统？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/89/a7/897a66f0c3cf12fa85d0802b7bb579a7.mp3"></audio>

你好，我是庄振运。

前面两讲我们讨论了CPU和内存，今天我们讨论第三个重要的主题：存储系统。现在是大数据时代，这些数据终归要保存到各种存储系统里面，以供读写和分析，因此讨论存储系统的性能问题就很有必要了。

狭义上的存储往往是硬件，比如磁盘、磁带还有固态硬盘。而广义上的存储系统除了指硬件的硬盘，还包括基于网络的存储系统，比如SAN（Storage Area Network, 存储区域网络）和NAS存储（Network Attached Storage，网络接入存储）。

各种存储系统各有优缺点，尤其是性能和成本，所以对不同的需求，我们要选择最合适的存储系统。

我们首先讲存储系统最重要的三大性能指标：IOPS、访问延迟和带宽，然后讲传统硬盘HDD（Hard Disk Drive）的性能。因为传统硬盘的特性相对简单直白（毕竟业界已经用了几十年了）。这之后再讲固态硬盘的性能（固态硬盘就是SSD，也叫Flash）。相对于传统硬盘，SSD的内部工作原理很不一样，这也就导致它们的性能特性大相径庭。 最后，我们再延伸到基于网络的存储系统，并且介绍几个常用的和存储相关的工具。

## 存储系统的三大性能指标

一个存储系统的性能最主要的是三个：**IOPS**、**访问延迟**、**吞吐率/带宽**。这三个指标其实是互相关联和影响的，但是我们一般还是分开来衡量。

**IOPS**（Input/Output Per Second），即每秒钟能处理的读写请求数量，这是衡量存储性能的主要指标之一。每个IO的请求都有自己的特性，比如读还是写，是顺序读写还是随机读写，IO的大小是多少等。

什么是顺序读写呢？就是访问存储设备中相邻位置的数据；随机读写呢，则是访问存储设备中非相邻位置的数据。对随机读写进行性能衡量时，一般假定IO大小是4KB。

既然IO有这些特点，所以我们讨论存储系统IOPS性能的时候，经常需要更加具体的描述。比如顺序读IOPS、随机写IOPS等。

IOPS的数值会随这样的参数不同而有很大的不同，这些参数的变化，包括读取和写入的比例、其中顺序读写及随机读写的比例、读写大小、线程数量及读写队列深度等。此外，系统配置等因素也会影响IOPS的结果，例如操作系统的设置、存储设备的驱动程序特点、操作系统后台运行的作业等。

**访问延迟**（Access Time）和**响应时间**（Response Time），指的是从发起IO请求，到存储系统把IO处理完成的时间间隔，常以毫秒（ms）或者微妙（us）为单位。对这一性能指标，我们通常会考虑它的平均值和高位百分数，比如P99、P95。

**吞吐率**（Throughput）或者带宽（Bandwidth），衡量的是存储系统的实际数据传输速率，通常以MB/s或GB/s为单位。一般来讲，IOPS与吞吐率是紧密相关的；它们之间的关系是，吞吐率等于IOPS和IO大小的乘积。

这个也很容易理解，比如对一个硬盘的读写IO是1MB，硬盘的IOPS是100，那么硬盘总的吞吐率就是100MB/s。需要强调的是，这里IO的具体特性很重要，比如是顺序还是随机，IO大小等。

还有一点要注意，有些存储系统会因为其**IO队列深度增加**，而获得更好的IO性能；比如吞吐率会升高，平均访问延迟会降低。

这是为什么呢？这是因为存储系统的IO队列处理机制，可以对IO进行重新排序，从而获得好的性能。比如，它可以合并几个相邻的IO，把随机IO重新排序为顺序IO等。

## HDD（传统硬盘）的性能

我们先从你熟知的传统硬盘开始讨论。对于传统硬盘，我们应该比较熟悉它的内部是如何操作的。

简单来说，当应用程序发出硬盘IO请求后，这个请求就会进入硬盘的IO队列。如果前面有其他IO，那么这个请求可能需要排队等待。当轮到这个IO来存取数据时，磁头需要机械运动到数据存放的位置，这就需要磁头寻址到相应的磁道，并旋转到相应的扇区，然后才是数据的传输。所以，讨论硬盘IO的性能时，需要充分考虑这一点。

我们有时候需要把**硬盘响应时间**和**硬盘访问时间**分开对待。它们之间的关系是，硬盘响应时间除了包括访问时间外，还包括IO排队的延迟，如下图所示。

<img src="https://static001.geekbang.org/resource/image/03/19/030b5f050f8230d70654dd1df78c3119.png" alt="">

我们如果拿起一块硬盘仔细看看，硬盘上面往往会标注后面三个参数，分别是平均寻址时间、盘片旋转速度，以及数据传输速度，这三个参数就可以提供给我们计算上述三个步骤的时间。

平均寻址时间一般是几个毫秒。平均旋转时间可以从硬盘转动速度RPM来算出。因为每个IO请求平均下来需要转半圈，那么如果硬盘磁头每分钟转一万圈（10K RPM），转半圈就需要3毫秒。

要注意的是，硬盘上面标注的数据传输速度参数往往是最大值，实际的数据传输时间要取决于IO的大小。

对于一块普通硬盘而言，我们前面讲常用的性能数字时也提过，随机IO读写延迟就是8毫秒左右，IO带宽大约每秒100MB，而随机IOPS一般是100左右。

硬盘的技术也在发展，现代的硬盘也有很多变种。比如采用了多磁头技术，或者几块硬盘组成磁盘阵列，这样的整体IO性能也会相应地提升。

## SSD（固态硬盘）的技术背景

讲完了传统硬盘，我们接着看看固态硬盘——SSD。SSD的内部工作方式和HDD大相径庭，我们来了解一下。

### 单元（Cell）、页面（Page）、块（Block）

当今的主流SSD是基于NAND的，它是将数字位存储在单元中。每个SSD单元可以存储一位（SLC，Single Level Cell，单级单元）、两位（MLC，多级单元）、三位（TLC，三级单元），甚至四位（QLC）。

SSD的特点是，对SSD单元的每次擦除，都会降低单元的寿命，因此每一个单元只能承受一定数量的擦除。所以，不同的SSD就有这几方面的考虑和平衡。单元存储的位数越多，制造成本就越少，SSD的容量也就越大。但是耐久性（擦除次数）也会降低。所以高端的SSD，比如企业级的，基本都是基于SLC的。

一个页面包括很多单元，典型的页面大小是4KB。页面也是读写的最小存储单位。我们知道，HDD可以直接对任何字节重写和覆盖；但是对SSD而言，不能直接进行上述的“覆盖”操作。SSD的一个页面里面的所有单元，一旦写入内容后就不能进行重写，必须和其它相邻页面一起，被整体擦除、重置。

在SSD内部，多个页面会组合成**块**。一个块的典型大小为512KB或1MB，也就是大约128或256页。块是擦除的基本单位，每次擦除，都是整个块内的所有页面都被重置。

### I/O和垃圾回收（Garbage Collection）

我们总结一下，对SSD的IO操作，一共有三种类型：**读取**、**写入**和**擦除**。读取和写入是以页为单位的，也就是说最少也要读取写入一个页面。

IO写入的延迟，具体取决于磁盘的历史状态，因为如果SSD已经存储了许多数据，那么对页的写入，有时需要移动已有的数据，这种情况下写入延迟就比较大。但多数情况下，读写延迟都很低，一般在微秒级别，远远低于HDD。

擦除是以块为单位。擦除速度相对很慢，通常为几毫秒。所以，对同步的IO请求，发出IO的应用程序，可能会因为块的擦除而经历很大的写入延迟。为了尽量地减少这样的场景发生，一块SSD最好保持一定数量的空闲块，这样可以保证SSD的写入速度足够快。

SSD内部有垃圾回收（GC）机制，它的目的就在于此，就是不断回收不用的块，进行擦除，从而产生新的空闲块来备用。这样可以确保以后的页写入能快速分配到一个全新的页。

<img src="https://static001.geekbang.org/resource/image/b3/6e/b3e76cd89e5471b21900c489f839396e.png" alt="">

### 写入放大（Write Amplification, or WA）

这是SSD相对于HDD的一个缺点，即实际写入SSD的物理数据量，有可能是应用层写入数据量的多倍。

这是因为，一方面页级别的写入需要移动已有的数据来腾空页面来写入。另一方面，GC的操作,也会移动用户数据来进行块级别的擦除。

所以，对SSD真正的写操作的数据，肯定比实际写的数据量大，这就是写入放大。因为一块SSD只能进行有限的擦除次数，也称为编程/擦除（P/E）周期，所以写入放大效用会缩短SSD的寿命。

### 耗损平衡（Wear Leveling）

对每一个块而言，一旦擦除造成的损耗达到最大数量，该块就会“死亡”，再也不能存储数据了。对于SLC类型的块，P/E周期的典型数目是十万次；对于MLC块，P/E周期的数目是一万；而对于TLC块，则可能是几千。为了确保整块SSD的容量、性能和可靠性，SSD内部需要对整个SSD的各块做平衡，尽量在擦除次数上保持类似。

SSD控制器具有这样一种机制，也叫“耗损平衡”，来实现这一目标。在损耗平衡操作时，数据在各个块之间移动，以实现均衡的损耗。但是这种机制也有害处，就是会对前面讲的写入放大推波助澜。

## SSD的性能和应用程序的设计

性能方面，SSD的IO性能相对于HDD来说，IOPS和访问延迟提升了上千倍，吞吐率也是提高了几十倍。但是SSD的缺点也很明显。主要有三个缺点：

1. 贵；
1. 容量小；
1. 易损耗。

好消息是，随着技术的发展，这三个缺点近几年在弱化。

如今，越来越多的应用程序采用SSD来减轻I/O性能瓶颈。许多测试和实践的结果都表明，与HDD相比，采用SSD带来了极大的应用程序性能提升。

但是，我想强调的一点是，在大多数采用SSD的部署方案中，SSD仅被视为一种“更快的HDD”，并没有真正发挥SSD的潜力。

我为什么这么说呢？因为尽管使用SSD作为存储时，应用程序可以获得更好的性能，但是这些收益，主要归因于**SSD提供的更高的IOPS和带宽**。

但是，SSD除了提供这些之外，它还有其它特点，比如易损耗，以及其独特的内部机制。如果应用程序的设计能充分考虑SSD的内部机制，设计出对SSD友好的应用程序，就可以更大程度地优化SSD，从而进一步提高应用程序性能，也可以延长SSD的寿命，并降低运营成本。关于这方面，我后面会有一讲专门讨论。

## 基于网络的存储系统

我们前面讨论了存储硬件，这些存储硬件可以直接安装在服务器上，构成单机系统。和单机系统和场景相对应，也有很多非单机使用的场景。

在非单机使用的场景里，这些存储硬件也被包装在各种基于网络的存储系统里。这样的存储系统也有很多种，比如DAS、NAS和SAN。

DAS（Directed Attached Storage）是直连式存储。这是以服务器为中心的存储系统，存储设备直接通过I/O总线连在服务器主机上。这种存储一般运行SATA或者SAS等协议，可以让网络的客户端直接使用。

NAS（Network Attached Storage）是网络接入存储。在NAS存储结构中，存储系统不再通过I/O总线只属于某个特定的服务器，而是通过网络接口直接与网络相连。NAS提供的是文件服务器的功能（比如NFS和CIFS），供客户通过网络访问。

SAN（Storage Area Network）是存储区域网络。SAN是一种以网络为中心的存储系统，通常有高性能专用网络（比如光纤）来支持，运行iSCSI等协议。

## 工具

最后，我们看看常用的存储系统性能监测和测试工具。存储系统的测试和监控命令工具非常多，下面简单介绍几个。

Linux系统上可以采用fio工具进行各种组合的IO测试。这些组合包括读写比例、随机还是顺序读写、IO大小等等。

IOMeter也是不错的测试磁盘性能的工具，比如可以测试I/O的传输速度和平均的I/O响应时间。

IOZone是一个文件系统基准测试工具，可以测试不同的操作系统中文件系统的读写性能。

Bonnie++是基于Linux平台的开源磁盘IO测试的工具，可以用它来测试磁盘和文件系统的I/O性能。

hdparm可以用来作跳过文件系统的纯硬件操作测试。

iostat这个工具可以查看进程发出IO请求的数量、系统处理IO请求的耗时、磁盘的利用率等，也可以分析进程与操作系统的交互过程中IO方面是否存在瓶颈。

## 总结

<img src="https://static001.geekbang.org/resource/image/a9/b3/a946f03c8cee8ac2a5ed4e0b875716b3.png" alt="">

存储系统，顾名思义，是用来存放我们各种程序和服务的数据。随着现代互联网服务的大数据化，几乎所有的业务都离不开存储系统的支持。

各种存储系统的基础是传统硬盘或者固态硬盘，这两种硬盘在成本、性能、大小方面各有千秋。我想起了宋代有首诗叫《雪梅》，里面比较了白雪和梅花的优缺点：

“梅须逊雪三分白，雪却输梅一段香。”

这句诗用来形容传统硬盘和固态硬盘的关系还挺合适的。

在实际使用中，我们要注意它们的性能优缺点，从而适当来作取舍。比如，如果系统对IOPS或者延迟要求很高，恐怕只有SSD才能满足要求。反之，如果数据量极大需要降低成本，那么只能选择磁盘或者磁带系统了。

## 思考题

你对SSD这种新型存储了解多少？你公司里面有没有系统使用SSD？它们碰到的性能问题有哪几个方面？运维和开发人员是怎么一起解决这些问题的？

欢迎你在留言区分享自己的思考，与我和其他同学一起讨论，也欢迎你把文章分享给自己的朋友。
