前言:算法
目前在萬兆網絡和SSD,包括NVMe SSD 都已經很是普及。隨着硬件的速度愈來愈快,性能的瓶頸會從硬件轉移到軟件。尤爲對於存儲引擎來講,性能相當重要。性能優化
先來看一下咱們會對數據存儲引擎模塊有什麼樣的需求。服務器
首先,確定是仍是可靠。由於咱們客戶的應用場景都大部分是核心的應用,數據可靠是要絕對保證的,沒有任何妥協的空間。網絡
其次是性能,目前在萬兆網絡和SSD,包括 NVMe SSD 都已經很是普及。隨着硬件的速度愈來愈快,性能的瓶頸會從硬件轉移到軟件。尤爲對於存儲引擎來講,性能相當重要。數據結構
除了追求絕對的性能之外,咱們還但願可以作到高效。咱們但願每個 CPU 指令都不被浪費。咱們追求用最少的 CPU 指令完成一次 IO 操做。這背後的緣由是,存儲硬件設備愈來愈快,目前最快的存儲已經能夠作到單次訪問只須要 10 納秒。而若是程序中加一次鎖,作一次上下文切換,可能幾百個納秒就過去了。若是不作到高效的話,目前的 CPU 可能徹底沒法發揮出 SSD 的性能。除了高效的使用 CPU 之外,咱們也要高效的使用內存資源,網絡帶寬資源。同時,因爲目前相同容量的 SSD 的價格還高於 HDD 的價格,因此咱們也儘量的節省磁盤空間的佔用,經過利用壓縮,去重等技術,提升 SSD 的空間使用效率。架構
最後,也是很是重要的一點,存儲引擎須要易於 Debug,並且要易於升級。對於軟件工程師來講,50% 以上的工做時間都是在作 Debug,而對存儲軟件工程師來講,這個比例可能更高。咱們但願作一個很是易於 Debug 的軟件產品,若是發現問題,能夠快速的定位並修復。升級也是同樣,如今軟件的迭代速度愈來愈快,咱們但願軟件能夠方便的易於升級,這樣咱們可讓用戶更快的使用上新版本的軟件,享受到新版本的功能,以及性能的優化。併發
接下來,咱們來看一下具體的實現。不少傳統的存儲廠商在實現存儲引擎的時候,每每會選擇把整個 IO 路徑的實現放在 Kernel Space 裏面。例如在上圖中,上層是一個核心的存儲引擎,下層是文件系統,塊設備,以及驅動。因爲網絡棧也是實如今內核中的,把存儲引擎放在內核裏面就能夠最大化性能,減小上下文切換(Context Switch)。異步
但這種實現有不少很是嚴重的問題,首先就是難於 Debug。若是你們作過內核開發,就會知道在內核中 Debug 是一件很是麻煩的事情。並且開發語言也只能用 C,不能用其餘語言。分佈式
同時,在內核裏面開發,升級會很是困難。一次升級,不論是 Bugfix,仍是增長新功能,均可能須要重啓整個服務器,這對於存儲系統來講代價是很是巨大的。還有一個很重要的因素就是故障域很是大。Kernel 裏面的模塊若是出問題,可能致使整個 Kernel 被污染,多是死鎖,多是 Kernel Panic。一般也是須要重啓服務器才能修復。微服務
既然有這麼多問題,那咱們在設計的時候確定不會選擇用 Kernel Space 的方式。咱們選擇在 Userspace,也就是用戶態實現咱們的存儲引擎。
在 User Space 實現,不少項目會選擇把存儲引擎構建在 LSM Tree 的數據結構上。LSM Tree 運行在文件系統之上。User Space 和 Kernel 比起來更靈活,能夠用各類語言;升級也很方便,只須要重啓一下進程就能夠,不須要重啓服務器;User Space 的故障只會影響到服務進程自己,並不會影響到 Kernel 的運行。但這種方式的問題就是性能不夠好,因爲 IO 仍是須要通過 Kernel,因此會產生上下文切換,這個切換就會引入性能的開銷。
接下來,咱們來講一下 LSM Tree。LSM Tree 的數據結構以及實現咱們在這裏就作不詳細介紹了。總的來講,LSM Tree 是不少存儲引擎的核心。
LSM Tree 的好處就是實現起來是相對簡單的,有不少開源的實現能夠參考,並且它對小塊數據寫入優化作的很是好,會將小塊數據合併,並批量寫入。
然而 LSM Tree 並非銀彈,它最大的問題因爲他的數據結構而致使的『讀放大』和『寫放大』。這個問題會有多嚴重呢。咱們能夠來看一下這個圖(編者按:參見上圖),這是一個對『讀寫放大』的測試結果。從圖中能夠看到,若是寫入 1GB 的數據,最終會產生 3 倍的數據寫入量,也就是 3 倍的『寫放大』。若是寫入 100G 的話,則會被放大到 14 倍,也就是說若是寫 100G 的數據,實際上在磁盤上會產生 1.4TB 的寫流量。而『讀放大』會更加嚴重,在這個場景下會放大到 300 多倍。這就違背了咱們最開始提到了咱們但願提升硬件效率的訴求。
LSM Tree 雖然有各類各樣的好處,可是因爲存在嚴重的『讀寫放大』問題,因此咱們並不會採用LSM Tree 來作數據存儲引擎。咱們能夠借鑑 LSM Tree 中優秀的思想,結合咱們本身的需求,實現一套存儲引擎。這個包含了數據分配,空間管理,IO 等邏輯。
接下來,咱們看到這個這個圖中還有一個文件系統。這個文件系統是實如今內核中的,在塊設備之上。你們比較常見的文件系統包括 ext4,xfs,btrfs 等,不少存儲引擎也是實如今文件系統之上的。然而咱們須要思考一下咱們是否真的須要一個文件系統。
首先,文件系統所提供的功能遠遠多於存儲引擎的需求。例如文件系統提供的 ACL 功能,Attribute 功能,多級目錄樹功能,這些功能對於一個專用的存儲引擎來講,都是不須要的。這些額外的功能常常會產生一些 Performance Overhead,尤爲是一些全局鎖,對性能影響很是嚴重。
其次,大部分文件系統在設計的時候,都是面向單一磁盤的設計方式,而不是面向多塊磁盤的。而通常存儲服務器上都會部署 10 塊,甚至更多的磁盤,並且有多是 SSD,有多是 HDD,也多是混合部署。
第三,不少文件系統在異步 IO 上支持的並很差,儘管支持異步 IO 的接口,但實際使用過程當中,偶爾仍是會有阻塞的狀況發生,這也是文件系統裏一個很是很差的地方。
順便在此給你們推薦一個Java架構方面的交流學習羣:698581634,裏面會分享一些資深架構師錄製的視頻資料:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系,主要針對Java開發人員提高本身,突破瓶頸,相信你來學習,會有提高和收穫。在這個羣裏會有你須要的內容 朋友們請抓緊時間加入進來吧。
最後一個問題,文件系統爲了保證數據和元數據的一致性,也會有 Journaling 的設計。但這些 Journaling 也會引入寫放大的問題。若是服務器上掛載了多個文件系統,單個文件系統的 Journaling 也沒法作到跨文件系統的原子性。
最終咱們在設計存儲引擎的時候,咱們選擇了拋棄文件系統,拋棄 LSM Tree,本身在作一個理想中的存儲引擎,去掉沒必要要的功能,儘量的避免寫放大。把咱們想要的功能直接實如今塊設備上。
咱們並無想要本身實現 Block Layer 這一層,這是由於 Linux Kernel 中,Block Layer 是很是薄的一層,裏面實現的算法也很是簡單,這些算法也都有參數可調,也都有辦法關閉掉,因此不會有太多額外的性能開銷。
左邊這個圖就是 ZBS 目前的實現方式。但這種方式最大的問題仍是性能,Block Layer 和 Driver 都運行在 Kernel Space,User Space 的存儲引擎的 IO 都會通過 Kernel Space,會產生 Context Switch。將來咱們會轉向右邊這個圖的方式,經過 SSD 廠家提供的 User Space 驅動,結合 PMD(Poll Mode Driver)引擎,以提供更好的性能。
接下來,咱們看一下 ZBS 的 User Space 存儲引擎具體的實現。
IO Scheduler 負責接收上層發下來的 IO 請求,構建成一個 Transaction,並提交給指定的 IO Worker。IO Worker 負責執行這個 Transaction。Journal 模塊負責將 Transaction 持久化到磁盤上,並負責 Journal 的回收。Performance Tier 和 Capacity Tire 分別負責管理磁盤上的空閒空間,以及把數據持久化到對應的磁盤上。
原文連接:http://stor.51cto.com/art/201810/585053.htm?utm_source=tuicool&utm_medium=referral