前言
為了提升我們的軟件性能,我們有多種方法,如合理的數據結構、優秀的算法,還有非常重要的一點就是:依據軟件所依附的硬件自身特性,設計能最大限度發揮硬件性能的軟件。根據計算機內存快但無法持久化,硬盤可以持久化但是慢的特性,我們設計了「緩存」這一策略來提升性能;根據硬盤隨機讀寫慢但順序讀寫快這一特性,Kafka 用自己獨特的方式來實現高吞吐量的隊列。如今,由于 SSD 出色的性能以及逐漸降低的價格,有逐步代替 HDD(傳統硬盤)的趨勢,而我們之前的大部分程序是基于 HDD 開發的,并不能最好的發揮 SSD 的性能,因此,我們有必要了解 SSD 的特性,以及如何寫更適合于 SSD 的程序。
SSD 介紹
SSD 是用固態電子存儲芯片陣列而制成的硬盤,由控制單元和存儲單元(FLASH芯片、DRAM芯片)組成。SSD 的每個數據位保存在由浮柵晶體管制成的閃存單元里。SSD整個都是由電子組件制成的,沒有像硬盤那樣的移動或者機械的部分。SSD 的內部架構如下:
1. 使用壽命
SSD 的數據保存在浮柵晶體管中,浮柵晶體管使用電壓來實現每個位的讀寫和擦除。寫晶體管有兩個方法:NOR 閃存和 NAND 閃存。目前大多數制造商都采用 NAND 閃存,NAND 閃存模塊的一個重要特征是,他們的閃存單元是損耗性的,因此它們有一個壽命。衡量壽命的單位是 PE周期(program/erase cycles)。
2. 性能
下圖是 SSD 與其他存儲介質的性能對比:
其中 SLC、MLC、TLC 可以理解為不同類型的 SSD,下面會有介紹。
2. 存儲結構
SSD 存儲結構分為單元、頁、塊三層,下面依次介紹:
-
單元(cell):單元是 SSD 存儲的最小單位,由于采用的閃存類型不同,各類閃存中的單元能存儲的數據也不同,目前有三種閃存單元類型:
- 單層單元(SLC),這種的晶體管只能存儲 1 個比特但壽命很長。
- 多層單元(MLC),這種的晶體管可以存儲 2 個比特,但是會導致增 加延遲時間和相對于SLC減少壽命。
- 三層單元(TLC),這種的晶體管可以保存 3 個比特,但是會有更高的延遲時間和更短的壽命。
頁(page):頁由許多單元組成,是我們讀寫 SSD 的最小單位,大多數硬盤的頁大小是 2KB、4KB、8 KB 或 16 KB。當我們讀 SSD 的數據時,最少讀取一頁(就算我們只需要 1 字節的數據);當我們寫數據時,就算只需要寫 1 字節,也會寫一頁,因此存在寫入放大的問題。頁不能被重復寫。
塊(block):塊由許多頁組成,是數據擦除的最小單元,大多數SSD每個塊有128或256個頁。這即表示一個塊的大小可能在 256 KB 到 4 MB 之間。之前我們說過,頁不能被重復寫,要修改頁的數據,必須先將頁所在的塊擦除,然后再重新寫入新值(塊中其他葉需要緩存然后再寫入)。
3. 數據更新
由于 SSD 不支持數據覆蓋寫入,因此對于數據的更新,只能將老的數據標記為過期,在另外一個空閑的地方寫入更新后的值。具體操作如下圖:
由上圖我們可知,更新一個已有數據的流程如下:
- 將老的數據所在的頁(PPN=0)標記為過期
- 在空閑頁(PPN=3)寫入新值 x'
- 當垃圾回收程序檢測到該塊(Block 1000)中存在過期數據(PPN=0),準備將其回收。而數據的擦除是以塊為單位的,因此,需要先將 Block 1000 中有效的數據拷貝到另外一個空閑塊(Block 2000),然后再將 Block 1000 擦除。
4. 損耗均衡
由于 SSD 存儲單元的擦除次數是有限的,而擦除的最小單位是塊,所以需要有一個機制來平衡各塊的擦除次數,從而使整個 SSD 的各部分損耗更加均衡,這一機制稱為損耗均衡。通過該技術,存儲數據會在不同的塊之間移動,以避免對同一塊的頻繁擦除。
5. 內部并行
因為物理限制的存在,異步 NAND 閃存 I/O 總線無法提供32-40 MB/s以上的帶寬。由于一塊 SSD 是由多個存儲芯片組成的,因此我們可以通過并行讀寫多個存儲芯片的方式來提升 SSD I/O 的性能。
SSD 內部將不同芯片中的多個塊稱為一個簇(clustered block),一次數據寫入可以并行的寫入到簇中的不同塊中。如下圖中,一次數據寫入可以拆封成多個并行寫入的任務,寫入到黃色虛線框的簇中。
6. 垃圾回收
上面我們已經講到,SSD 中的數據是不能被重復寫的,必須先擦除然后再寫,而擦除的速度非常慢(通常為毫秒級),SSD控制芯片會執行垃圾回收操作,即回收使用過的塊,確保后續寫操作能夠快速分配到可用的塊。
設計 SSD 友好的程序
針對上述介紹的 SSD 特性,在開發程序時我們可以做一些針對性的設計,以提高 SSD 的性能,還有延長 SSD 的使用壽命。
1. 對齊寫入
SSD 數據寫入的最小單元是頁,因此,如果我們寫入的數據小于頁大小,會有兩個危害:
- 空間浪費:如果想再次利用頁中的空閑部分,必須擦除所處的整個塊
- 讀取效率低:一次讀取讀到的有效數據少,讀相同數據需要更多次讀操作
對齊寫入指一次數據寫入是頁大小的整數倍,如果數據不夠足,可以先緩沖在內存中,例如 Twitter 的 fatcache 就是湊夠了1MB 才會寫 SSD。
2. 相關的數據一起寫
相關的數據一起寫指的是對于經常被一起訪問的數據,寫的時候盡量一次同時寫入,這樣做有兩個層面的好處:
- 由于讀寫的最小單位是頁,相關的數據寫在一頁上,數據讀的時候效率高
- 由于 SSD 存在內部并行的特性,一次將大量(簇大小的整數倍)相關的數據同時寫入,內部會優化并行寫入到各個存儲芯片,讀的時候也能并行讀取,可以提升讀的性能
3. 將冷熱數據分開寫
將冷熱數據分開寫主要是防止由于冷熱數據范圍頻率不同而導致的寫入放大,如果將冷熱數據寫一起,將會有兩個層面的缺點:
- 如果冷熱數據在同一個頁上:數據塊小于16KB或不對齊16KB時,更新熱數據不得不讀改寫冷數據。
- 如果冷熱數據在同一個塊上:垃圾回收熱數據時,不得不搬移并重寫冷數據。
4. 緩存熱數據
我們知道 SSD 對于數據更新,只能通過擦除-寫入的方式,對于非常高頻變化的數據,要對數據所在塊進行頻繁的擦除-寫入,對整體性能會有較大影響。所以建議對熱數據進行程序的緩存,而不是寫入磁盤。
5. 讀寫分離
混合的小讀寫交叉的工作負載會妨礙內部緩存和預讀取機制正常工作,并會導致吞吐量下降。最好的辦法是避免同時發生的讀操作和寫操作,并將其以一個接一個的大數據塊的形式實現,數據塊大小推薦和簇大小相同。舉個例子,如果要更新1000個文件,你可以遍歷文件逐一讀寫,但會很慢。如果一次讀取1000個文件然后一次寫回1000個文件將會好很多。
6. 避免長時間大數據寫入
SSD 內部存在異步垃圾回收進程,一般情況下不會影響正常的 I/O,但是如果長時間大數據寫入,可能存在垃圾回收速度跟不上寫入速度的情況,導致寫入請求需要等待垃圾回收進程擦除塊,影響性能。
7. 避免就地(in-place)更新優化
由于HDD在查找數據時又尋道時間,為了避免尋道產生的延遲,應用程序常常被優化成就地更新。但這個優化對于 SSD 并不適用,因為 SSD 沒有尋道時間,且不支持覆蓋寫,更新數據必須先擦除,再寫入,所以就地更新反而會造成性能下降,下圖是 SSD 與 HDD 就地更新的性能對比:
8. 對于大的(大于簇的大?。┳x寫操作,單線程比多線程好
造成這個現象的原因就是我們上面說到過的 SSD 存在內部并行的機制。
- 對于大的寫入,由于內部并行機制的存在,單線程也能達到多線程的性能,并且,一個大的單線程寫入比并發寫入延遲時間更短。因此,在可用的時候,使用單線程大寫入是最好的。
- 對于大的讀取,單線程讀不僅可以依靠內部并行機制達到多線程讀同樣的性能,而且可以更好的使用預讀取機制,因此比多線程并發讀更優。
9. 對于小數據的讀寫,多線程比單線程好
如果數據太小且沒辦法進行合并后讀寫,就無法使用其內部并行機制,因此多線程更優。
總結
本文分析了一些 SSD 的特性,以及針對這些特性,我們應該如何設計更適合于 SSD 的程序。SSD 是提升磁盤性能的利器,目前看也大有替代傳統 HDD 的趨勢,因此建議廣大工程師都對其有所了解。當然,SSD 目前也在快速的發展之中,可能有些特性會隨著發展而改變,希望大家能及時同步最新知識。