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.

114 lines
13 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.

# 47 | SSD硬盘如何完成性能优化的KPI
如果你平时用的是Windows电脑你会发现用了SSD的系统盘就不能用磁盘碎片整理功能。这是因为一旦主动去运行磁盘碎片整理功能就会发生一次块的擦除对应块的寿命就少了一点点。这个SSD的擦除寿命的问题不仅会影响像磁盘碎片整理这样的功能其实也很影响我们的日常使用。
我们的操作系统上并没有SSD硬盘上各个块目前已经擦写的情况和寿命所以它对待SSD硬盘和普通的机械硬盘没有什么区别。
我们日常使用PC进行软件开发的时候会先在硬盘上装上操作系统和常用软件比如Office或者工程师们会装上VS Code、WebStorm这样的集成开发环境。这些软件所在的块写入一次之后就不太会擦除了所以就只有读的需求。
一旦开始开发我们就会不断添加新的代码文件还会不断修改已经有的代码文件。因为SSD硬盘没有覆写Override的功能所以这个过程中其实我们是在反复地写入新的文件然后再把原来的文件标记成逻辑上删除的状态。等SSD里面空的块少了我们会用“垃圾回收”的方式进行擦除。这样我们的擦除会反复发生在这些用来存放数据的地方。
![](https://static001.geekbang.org/resource/image/09/6e/09a9566eae60610b0f49d7e24ce4ee6e.jpeg)
有一天,这些块的擦除次数到了,变成了坏块。但是,我们安装操作系统和软件的地方还没有坏,而这块硬盘的可以用的容量却变小了。
## 磨损均衡、TRIM和写入放大效应
### FTL和磨损均衡
那么,我们有没有什么办法,不让这些坏块那么早就出现呢?我们能不能,匀出一些存放操作系统的块的擦写次数,给到这些存放数据的地方呢?
相信你一定想到了其实我们要的就是想一个办法让SSD硬盘各个块的擦除次数均匀分摊到各个块上。这个策略呢就叫作**磨损均衡**Wear-Leveling。实现这个技术的核心办法和我们前面讲过的虚拟内存一样就是添加一个间接层。这个间接层就是我们上一讲给你卖的那个关子就是FTL这个**闪存转换层**。
![](https://static001.geekbang.org/resource/image/6e/91/6e78f8da0320dc9b392b9d35ecf42091.jpeg)
就像在管理内存的时候我们通过一个页表映射虚拟内存页和物理页一样在FTL里面存放了**逻辑块地址**Logical Block Address简称LBA到**物理块地址**Physical Block Address简称PBA的映射。
操作系统访问的硬盘地址其实都是逻辑地址。只有通过FTL转换之后才会变成实际的物理地址找到对应的块进行访问。操作系统本身不需要去考虑块的磨损程度只要和操作机械硬盘一样来读写数据就好了。
操作系统所有对于SSD硬盘的读写请求都要经过FTL。FTL里面又有逻辑块对应的物理块所以FTL能够记录下来每个物理块被擦写的次数。如果一个物理块被擦写的次数多了FTL就可以将这个物理块挪到一个擦写次数少的物理块上。但是逻辑块不用变操作系统也不需要知道这个变化。
这也是我们在设计大型系统中的一个典型思路也就是各层之间是隔离的操作系统不需要考虑底层的硬件是什么完全交由硬件的控制电路里面的FTL来管理对于实际物理硬件的写入。
### TRIM指令的支持
不过操作系统不去关心实际底层的硬件是什么在SSD硬盘的使用上也会带来一个问题。这个问题就是操作系统的逻辑层和SSD的逻辑层里的块状态是不匹配的。
我们在操作系统里面去删除一个文件其实并没有真的在物理层面去删除这个文件只是在文件系统里面把对应的inode里面的元信息清理掉这代表这个inode还可以继续使用可以写入新的数据。这个时候实际物理层面的对应的存储空间在操作系统里面被标记成可以写入了。
所以,其实我们日常的文件删除,都只是一个操作系统层面的逻辑删除。这也是为什么,很多时候我们不小心删除了对应的文件,我们可以通过各种恢复软件,把数据找回来。同样的,这也是为什么,如果我们想要删除干净数据,需要用各种“文件粉碎”的功能才行。
这个删除的逻辑在机械硬盘层面没有问题因为文件被标记成可以写入后续的写入可以直接覆写这个位置。但是在SSD硬盘上就不一样了。我在这里放了一张详细的示意图。我们下面一起来看看具体是怎么回事儿。
![](https://static001.geekbang.org/resource/image/72/d7/72b3fc74ff567e7a0ec1f4071da946d7.jpeg)
一开始操作系统里面有好几个文件不同的文件我用不同的颜色标记出来了。下面的SSD的逻辑块里面占用的页我们也用同样的颜色标记出来文件占用的对应页。
当我们在操作系统里面,删除掉一个刚刚下载的文件,比如标记成黄色 openjdk.exe 这样一个jdk的安装文件在操作系统里面对应的inode里面就没有文件的元信息。
但是这个时候我们的SSD的逻辑块层面其实并不知道这个事情。所以在逻辑块层面openjdk.exe 仍然是占用了对应的空间。对应的物理页,也仍然被认为是被占用了的。
这个时候如果我们需要对SSD进行垃圾回收操作openjdk.exe 对应的物理页仍然要在这个过程中被搬运到其他的Block里面去。只有当操作系统再在刚才的inode里面写入数据的时候我们才会知道原来的些黄色的页其实都已经没有用了我们才会把它标记成废弃掉。
所以在使用SSD的硬盘情况下你会发现操作系统对于文件的删除SSD硬盘其实并不知道。这就导致我们为了磨损均衡很多时候在都在搬运很多已经删除了的数据。这就会产生很多不必要的数据读写和擦除既消耗了SSD的性能也缩短了SSD的使用寿命。
为了解决这个问题现在的操作系统和SSD的主控芯片都支持**TRIM命令。**这个命令可以在文件被删除的时候让操作系统去通知SSD硬盘对应的逻辑块已经标记成已删除了。现在的SSD硬盘都已经支持了TRIM命令。无论是Linux、Windows还是MacOS这些操作系统也都已经支持了TRIM命令了。
### 写入放大
其实TRIM命令的发明也反应了一个使用SSD硬盘的问题那就是SSD硬盘容易越用越慢。
当SSD硬盘的存储空间被占用得越来越多每一次写入新数据我们都可能没有足够的空白。我们可能不得不去进行垃圾回收合并一些块里面的页然后再擦除掉一些页才能匀出一些空间来。
这个时候从应用层或者操作系统层面来看我们可能只是写入了一个4KB或者4MB的数据。但是实际通过FTL之后我们可能要去搬运8MB、16MB甚至更多的数据。
我们通过“**实际的闪存写入的数据量 / 系统通过FTL写入的数据量 = 写入放大**”可以得到写入放大的倍数越多意味着实际的SSD性能也就越差会远远比不上实际SSD硬盘标称的指标。
而解决写入放大,需要我们在后台定时进行垃圾回收,在硬盘比较空闲的时候,就把搬运数据、擦除数据、留出空白的块的工作做完,而不是等实际数据写入的时候,再进行这样的操作。
## AeroSpike如何最大化SSD的使用效率
讲到这里相信你也发现了想要把SSD硬盘用好其实没有那么简单。如果我们只是简单地拿一块SSD硬盘替换掉原来的HDD硬盘而不是从应用层面考虑任何SSD硬盘特性的话我们多半还是没法获得想要的性能提升。
不过既然清楚了SSD硬盘的各种特性我们就可以依据这些特性来设计我们的应用。接下来我就带你一起看一看AeroSpike这个专门针对SSD硬盘特性设计的Key-Value数据库键值对数据库是怎么利用这些物理特性的。
首先AeroSpike操作SSD硬盘并没有通过操作系统的文件系统。而是直接操作SSD里面的块和页。因为操作系统里面的文件系统对于KV数据库来说只是让我们多了一层间接层只会降低性能对我们没有什么实际的作用。
其次AeroSpike在读写数据的时候做了两个优化。在写入数据的时候AeroSpike尽可能去写一个较大的数据块而不是频繁地去写很多小的数据块。这样硬盘就不太容易频繁出现磁盘碎片。并且一次性写入一个大的数据块也更容易利用好顺序写入的性能优势。AeroSpike写入的一个数据块是128KB远比一个页的4KB要大得多。
另外在读取数据的时候AeroSpike倒是可以读取512字节Bytes这样的小数据。因为SSD的随机读取性能很好也不像写入数据那样有擦除寿命问题。而且很多时候我们读取的数据是键值对里面的值的数据这些数据要在网络上传输。如果一次性必须读出比较大的数据就会导致我们的网络带宽不够用。
因为AeroSpike是一个对于响应时间要求很高的实时KV数据库如果出现了严重的写放大效应会导致写入数据的响应时间大幅度变长。所以AeroSpike做了这样几个动作
第一个是持续地进行磁盘碎片整理。AeroSpike用了所谓的高水位High Watermark算法。其实这个算法很简单就是一旦一个物理块里面的数据碎片超过50%,就把这个物理块搬运压缩,然后进行数据擦除,确保磁盘始终有足够的空间可以写入。
第二个是在AeroSpike给出的最佳实践中为了保障数据库的性能建议你只用到SSD硬盘标定容量的一半。也就是说我们人为地给SSD硬盘预留了50%的预留空间以确保SSD硬盘的写放大效应尽可能小不会影响数据库的访问性能。
![](https://static001.geekbang.org/resource/image/35/60/354d34d871dda3ef5a4792a1fe1fb860.jpeg)
正是因为做了这种种的优化在NoSQL数据库刚刚兴起的时候AeroSpike的性能把Cassandra、MongoDB这些数据库远远甩在身后和这些数据库之间的性能差距有时候会到达一个数量级。这也让AeroSpike成为了当时高性能KV数据库的标杆。你可以看一看InfoQ出的这个[Benchmark](https://www.infoq.com/news/2013/04/NoSQL-Benchmark/)里面有2013年的时候这几个NoSQL数据库巨大的性能差异。
## 总结延伸
好了,现在让我们一起来总结一下今天的内容。
因为SSD硬盘的使用寿命受限于块的擦除次数所以我们需要通过一个磨损均衡的策略来管理SSD硬盘的各个块的擦除次数。我们通过在逻辑块地址和物理块地址之间引入FTL这个映射层使得操作系统无需关心物理块的擦写次数而是由FTL里的软件算法来协调到底每一次写入应该磨损哪一块。
除了磨损均衡之外操作系统和SSD硬件的特性还有一个不匹配的地方。那就是操作系统在删除数据的时候并没有真的删除物理层面的数据而只是修改了inode里面的数据。这个“伪删除”使得SSD硬盘在逻辑和物理层面都没有意识到有些块其实已经被删除了。这就导致在垃圾回收的时候会浪费很多不必要的读写资源。
SSD这个需要进行垃圾回收的特性使得我们在写入数据的时候会遇到写入放大。明明我们只是写入了4MB的数据可能在SSD的硬件层面实际写入了8MB、16MB乃至更多的数据。
针对这些特性AeroSpike这个专门针对SSD硬盘特性的KV数据库设计了很多的优化点包括跳过文件系统直写硬盘、写大块读小块、用高水位算法持续进行磁盘碎片整理以及只使用SSD硬盘的一半空间。这些策略使得AeroSpike的性能在早年间远远超过了Cassandra等其他NoSQL数据库。
可以看到,针对硬件特性设计的软件,才能最大化发挥我们的硬件性能。
## 推荐阅读
如果你想要基于SSD硬盘本身的特性来设计开发你的系统我推荐你去读一读AeroSpike的这个[PPT](https://www.slideshare.net/AerospikeDB/getting-the-most-out-of-your-flashssds)。AeroSpike是市面上最优秀的KV数据库之一通过深入地利用了SSD本身的硬件特性最大化提升了作为一个KV数据库的性能。真正在进行系统软件开发的时候了解硬件是必不可少的一个环节。
## 课后思考
在SSD硬盘的价格大幅度下降了之后LFS也就是Log-Structured File System在业界出现了第二春。你可以去了解一下什么是LFS以及为什么LFS特别适合SSD硬盘。
欢迎在留言区分享你了解到的信息,和大家一起交流。如果有收获,你可以把这篇文章分享给你的朋友。