You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
16 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 29 | 如何彻底发挥SSD的潜力
你好,我是庄振运。
今天是“性能工程实践”这个模块的最后一讲我们来讨论一种“软硬件结合”的性能工程优化实践与SSD硬件有关。现在SSD用的越来越普遍的情况你一定非常清楚但是你设计的应用程序软件真的充分利用了SSD的特点并发挥SSD的潜力了吗
要知道SSD可不仅仅是“更快的HDD”。
SSD的好处显而易见它作为存储时应用程序可以获得更好的I/O性能。但是这些收益主要归因于SSD提供的更高的IOPS和带宽。如果你因此只将SSD视为一种“更快的HDD”那就真是浪费了SSD的潜力。
如果你在设计软件时能够充分考虑SSD的工作特点把应用程序和文件系统设计为“对SSD友好”会使服务性能有个质的飞跃。
今天我们就来看看,**如何在软件层进行一系列SSD友好的设计更改**。
## 为什么要设计SSD友好的软件
设计对SSD友好的软件有什么好处呢简单来说你可以获得三种好处
1. 提升应用程序等软件的性能;
2. 提高SSD的 I/O效率
3. 延长SSD的寿命。
先看第一种好处——**更好的应用程序性能**。在不更改应用程序设计的情况下简单地采用SSD可以获得性能提升但无法获得最佳性能。
我为你举个例子来说明。我们曾经有一个应用程序它需要不断写入文件以保存数据主要性能瓶颈就是硬盘I/O。使用HDD时最大应用程序吞吐量为142个查询/秒QPS。无论我们对应用程序设计进行什么样的更改或调优这就是使用HDD可以获得的最好性能了。
而当迁移到具有相同应用程序的SSD时吞吐量提高到了20,000 QPS速度提高了140倍。这种提高主要来自SSD提供的更高的IOPS。你是不是觉得与HDD相比应用程序吞吐量有了显着提高性能已经很好了
但这并不是SSD能实现的最佳性能。
我们对应用程序设计进行了优化使其对SSD友好之后应用程序吞吐量提高到100,000QPS与简单设计相比提高了4倍。
你可能会问,这是如何做到的?
这其中的秘密,就是**使用多个并发线程来执行I/O**。如下图所示,这利用了**SSD的内部并行性**。
![](https://static001.geekbang.org/resource/image/d2/a0/d2b82a4a2b47c03aea570f183c49b8a0.png)
这里你需要注意的是在这个系统中多个I/O线程对HDD是毫无益处的。因为HDD只有一个磁头所以用多个I/O线程并不能提高旧系统的吞吐量。
第二种好处是**更高效的存储I/O**。
我在[第17讲](https://time.geekbang.org/column/article/185154)中提到过SSD上的最小内部I/O单元是一页比如4KB大小。因此对SSD的单字节读/写必须在页面级进行。应用程序对SSD的写操作可能会导致对SSD上的物理写操作变大这就是“写入放大参见[第23讲](https://time.geekbang.org/column/article/190151))”。
因为有这个特性如果应用程序的数据结构或I/O对SSD不友好就会让写放大效果更大导致SSD的I/O不能被充分利用。
设计SSD友好软件的最后一个好处是**延长SSD的寿命**。
SSD会磨损是因为每个存储单元只能维持有限数量的写入擦除周期。实际上SSD的寿命取决于四个因素
1. SSD大小
2. 最大擦除周期数
3. 写入放大系数
4. 应用程序写入速率
例如假设有一个1TB大小的SSD一个写入速度为100MB/秒的应用程序和一个擦除周期数为10,000的SSD。当写入放大倍数为4时SSD仅可持续10个月。具有3,000个擦除周期和写入放大系数为10的SSD只能使用一个月。
你也知道相对于HDD而言SSD的成本比较高我们自然希望自己设计的应用程序对SSD友好从而延长SSD的使用寿命。
## 怎么设计对SSD友好的软件
说了这么多设计SSD友好软件的好处那我们具体该从哪里入手呢
其实在软件领域有很多地方都可以做对SSD友好的设计比如文件系统、数据库系统、数据基础架构层和应用程序。接下来我会一一为你介绍。尤其是应用程序这个领域它是这一讲的重点。
第一个可以对SSD友好的领域是**文件系统**。文件系统层直接处理存储因此我们需要在此级别进行优化设计更改来更有效地发挥SSD的特长。一般而言这些设计更改集中在**SSD和HDD迥异的三个关键差异特征**上:
* SSD的随机访问与顺序访问具有相同的性能
* 需要在块级别进行擦除后才能重写;
* 内部损耗均衡的机制会导致写入放大。
现在比较流行的有两种对SSD友好的文件系统。第一种是适用于SSD的通用文件系统主要支持Trim的新功能比如Ext4和Btrfs。第二种是专门为SSD设计的文件系统。基本思想是采用日志结构的数据布局相对于B树或Htree来容纳SSD的“复制-修改-写入”属性比如NVFS非易失性文件系统、FFS / JFFS2和F2FS。
除了文件系统,**数据库系统**也可以设计成对SSD友好。
SSD和HDD之间的差异在数据库设计中尤其重要。近几十年来数据库的各个组件比如查询处理、查询优化和查询评估等都已经在考虑HDD特性的情况下进行了诸多优化。
举个例子因为普通硬盘的随机访问比顺序访问要慢得多所以数据库组件会尽量减少随机访问。而使用SSD时类似这样的假设就不成立了。
因此业界设计了新的、对SSD友好的数据库。对SSD友好的数据库主要有两种第一种是专门针对SSD的数据库例如AreoSpike这种数据库主要采用对SSD友好的Join算法第二种是HDD和SSD混合的数据库一般是使用SSD来缓存数据。
第三个领域是**数据基础架构层**。
对于分布式数据系统的设计而言,数据来源大体上有两个地方:计算机上的本地磁盘或另一台计算机上的内存。这两个来源哪个更快更高效呢?还真不一定。随着技术的演化,业界也一直在争论。
过去很多年这样的争论比较倾向于远程计算机的内存因为速度更快Memcached就是一个例子。传统的本地HDD访问延迟大约是好几个毫秒而远程内存访问的延迟包括了RAM访问延迟和网络传输延迟也仅处于微秒级。同时远程内存的I/O带宽与本地HDD大致相同甚至更多。因此远端的内存反而比本地的硬盘的访问时间更短。
而SSD的出现正在改变这个趋势和业界的决定。使用SSD作为存储设备后本地SSD变得比远程内存访问更为高效。
首先SSD的I/O延迟降低到了微秒级而I/O带宽可是比HDD高一个数量级。这些结果就导致了数据基础架构层的新设计。比如新设计更希望尽可能与应用程序**共同分配数据**,以避免额外的节点和网络的限制,从而降低系统的复杂性和成本。
这么说可能你的感受不太明显我来用Netflix为你举例说明一下。Netflix就是采用这种设计的公司之一。Netflix公司曾经采用memcached来缓存Cassandra层数据。假设Netflix需要缓存10TB的数据如果每个内存缓存节点在RAM中保存100GB数据则需要部署100个内存缓存节点。后来Netflix只使用10个Cassandra节点并为每个Cassandra节点配备1TB SSD来完全取代memcached层。
你看采用这种设计就不需要100个Memcached节点只需10台配备SSD的节点节省了大量成本。
在**应用程序层**我们也可以对SSD进行友好的设计以获得前面提到的三种好处更好的应用程序性能、更高效的I/O和更长的SSD寿命
在这一领域我总结了七大SSD友好的设计原则大体上分为三类数据结构、I/O处理和线程使用。
![](https://static001.geekbang.org/resource/image/a0/2d/a087788f0d74a2863366ca456f8c362d.png)
### 1.数据结构:避免就地更新优化
传统的HDD的寻址延迟很大因此使用HDD的应用程序通常会进行各种优化以执行不需要寻址的就地更新比如只在一个文件后面写入。
比如下图所示在执行随机更新时吞吐量一般只能达到约170 QPS而对于同一个HDD就地更新可以达到280QPS远高于随机更新。
![](https://static001.geekbang.org/resource/image/ff/3b/fff29bfb9e14f2ff93e0637e8636333b.png)
不过在设计与SSD配合使用的应用程序时这些考虑就没什么意义了。对SSD而言随机读写和顺序读写性能类似就地更新不会获得任何IOPS优势。
此外就地更新实际上是会导致SSD性能下降的。包含数据的SSD页面无法直接重写因此在更新存储的数据时必须先将相应的SSD页面读入SSD缓冲区然后将数据写入干净的页面。 SSD中的“读取-修改-写入”过程与HDD上的直接“仅写入”行为形成了鲜明的对比。
相比之下SSD上的随机更新就不会引起读取和修改步骤即仅仅“写入”因此速度更快。
使用SSD以上相同的应用程序可以通过**随机更新**或**就地更新**来达到大约2万QPS而且随机更新和就地更新的吞吐量大体相似随机更新其实还稍微好些。如下图所示。
![](https://static001.geekbang.org/resource/image/7a/0b/7aa33a3abeef4b908f7560b23db6c20b.png)
### 2.数据结构:区分热、冷数据
几乎所有处理存储的应用程序,磁盘上存储的数据的访问概率均不相同。
用一个需要跟踪活动用户活动的社交网络应用程序来给你举个例子。对于用户数据存储简单的解决方案是基于用户属性例如注册时间将所有用户压缩在同一位置例如某个SSD上的文件。以后需要更新热门用户的活动时SSD需要在页面级别进行访问即读取/修改/写入)。
因此如果用户的数据大小是小于一页那么每次读取这个用户的数据附近的用户数据也将一起被访问。如果应用程序其实并不需要附近用户的数据那么额外的数据访问不仅会浪费I/O带宽而且会不必要地磨损SSD。
为了缓解这种性能问题在将SSD用作存储设备时应将热数据与冷数据分开。以不同级别或不同方式来进行分隔。例如把它们存在不同的文件、文件的不同部分或不同的表格里。
### 3.数据结构:采用紧凑的数据结构
在SSD的世界中最小的更新单位是页面4KB因此即使是一个字节的更新也将导致至少4KB SSD写入。由于写入放大的效果实际写入SSD的字节可能远大于4KB。读取操作也是类似因为OS具有预读机制会预先主动地读入文件数据以期改善读取文件时的缓存命中率。
所以当将数据保留在SSD上时你最好使用紧凑的数据结构避免分散的更新以获得更快的应用程序性能、更有效的存储I/O以及节省SSD的寿命。
### 4\. I/O处理避免长而繁重的持续写入
SSD通常具有GC机制不断地回收存储块以供以后使用。GC可以用后台或前台的方式工作。 SSD控制器通常保持一个空闲块的阈值。每当可用块数下降到阈值以下时后台GC就会启动。由于后台GC是异步发生的即非阻塞因此它不会影响应用程序的I/O延迟。但是如果块的请求速率超过了GC速率并且后台GC无法跟上则将触发前台GC。
在前台GC期间必须即时擦除即阻塞每个块以供应用程序使用这时发出写操作的应用程序所经历的**写延迟**就会受到影响。具体来说释放块的前台GC操作可能会花费数毫秒以上的时间从而导致较大的应用程序I/O延迟。
因此你最好避免进行长时间的大量写入操作这样就可能永远不触发前台GC。
### 5\. I/O处理避免SSD存储太满
SSD磁盘存储的满存程度会影响写入放大系数和GC导致的写入性能。在GC期间需要擦除块以创建空闲块。擦除块前需要移动并保留有效数据才能获得空闲块。有时为了获得一个空闲块需要压缩好几个存储块。而每个空闲块的生产需要压缩的块数取决于磁盘的空间使用率。
假设磁盘满百分比平均为A要释放一个块则需要压缩1 /1-A块。显然SSD的空间使用率越高将需要移动更多的块以释放一个块这将占用更多的资源并导致更长的I/O等待时间。
例如如果A=80则大约移动五个数据块以释放一个块。当A=95将移动约20个块。
### 6.线程使用多个线程执行小的I/O
SSD内部大量使用了并行的设计这种并行表现在多个层面上。一个I/O线程无法充分利用这些并行性会导致访问时间更长。而使用多个线程就可以充分利用SSD内部的并行性了。
SSD可以有效地在可用通道之间分配读写操作从而提供高水平的内部I/O并发性。例如我们使用一个应用程序执行10KB写入I/O10KB算是比较小的IO大小。使用一个I/O线程它可以达到115MB/秒。使用两个线程基本上使吞吐量加倍使用四个线程再次将其加倍使用八个线程可达到约500MB/秒,如下图所示。
![](https://static001.geekbang.org/resource/image/89/76/8984620c393a877fb47ecab219a41076.png)
你可能会很自然地问出一个问题这里的“小”IO到底有多小
答案是只要是不能充分利用内部并行性的任何I/O大小都被视为“小”。例如SSD页面大小为4KB内部并行度为16则阈值应约为64KB。
### 7.线程使用较少的线程来执行大I/O
第七个原则同样关于线程,它与第六条原则相对应(但并不矛盾)。
对于大型I/OSSD内部已经充分优化使用了SSD的内部并行性因此应使用更少的线程即小于四个以实现最大的I/O吞吐量。从吞吐量的角度来看用太多线程不会有太大益处。更重要的是使用太多线程可能导致线程之间的资源竞争以及诸如OS级的预读和回写之类的后台活动。
例如根据我们的实验当写入大小为10MB时一个线程可以达到414MB/秒两个线程可以达到816MB/秒而四个线程达到912MB/秒八个线程实际上只有520MB/秒。如下图所示。
![](https://static001.geekbang.org/resource/image/98/99/98feb65d9b624e057baeb97f0af6c599.png)
## 总结
这一讲我们讨论了软硬件结合的优化。
与使用HDD的应用程序相比使用SSD的应用程序通常具有更好的性能水平。但是如果不更改应用程序设计则应用程序是无法获得最佳性能的。因为SSD的工作方式不同于HDD。
为了发挥SSD的全部性能潜能应用程序设计必须对SSD友好。
![](https://static001.geekbang.org/resource/image/48/d0/485e47b6f1103ba4304e436d066f62d0.png)
唐代的王维有一首《少年行》,讲几个好哥们一起喝酒:“新丰美酒斗十千,咸阳游侠多少年。相逢意气为君饮,系马高楼垂柳边。” 说的是性情投机的好朋友一起互相帮助,就会互相促进。
硬件和软件也是如此,如果互相对对方友好,互相都受益,总体性能也就更高。
我们的软件系统如果能充分考虑硬件比如SSD的特性做出的设计就会获得更好的性能和稳定性。基于这一点我在这一讲里面提出了七个对SSD友好的软件设计原则分为三类数据结构、I/O处理和线程使用。你在设计使用SSD存储的软件时可以参考采用。
## 思考题
你正在开发、维护、使用的系统有没有使用SSD作为存储的如果有这个系统有没有考虑到SSD的特殊机制和寿命问题
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。