ScyllaDB的Userspace IO Scheduler實現

1. 關於ScyllaDB

ScyllaDB號稱世界上最快的NoSQL資料庫。它兼容Cassandra協議,性能優於Canssandra資料庫(據ScyllaDB給出的benchmark, 性能要優於10倍)。ScyllaDB由Avi Kivity, Dor Laor團隊開發(這兩位都是業界大牛)。

2. 為什麼要實現用戶態IO調度器

雖然磁碟技術在近10年有長足進步,特別是SSD盤的出現,為存儲系統提供了可觀的IO能力。即便如此,磁碟的IO速度遠遠落後於內存,更別提CPU了。在存儲系統中,存在激烈的磁碟IO競爭。這些IO產來自於系統的讀,寫,後台整理數據等動作。在沒有合理調度的請情況下,這些來自不同動作的磁碟IO會嚴重影響系統服務質量。例如,後台整理數據產生的IO消耗了系統所有磁碟IO能力,那麼系統就無法提供讀寫服務了。

由此可見,合理高效的磁碟IO對存儲系統顯得尤為重要。

2.1 系統磁碟IO過載會降低存儲系統服務質量

首先,放一張IO Stack全景圖(這是一張很經典的圖)。

從這個圖中,我們可以看到IO Stack中存在不同級別的隊列。這些隊列用於不同組件之間的通訊或傳遞數據。然而隊列的長度是有限制的。

由於磁碟的處理能力遠遠低於CPU的處理能力。大量的IO請求會被緩存在IO-Stack中的隊列里:磁碟硬體隊列,Block Layer 隊列,文件系統隊列。通常我們還很難判斷某個IO請求當前到底在哪個隊列里。

用戶線程需要一個關於系統IO能力的全局視圖,根據當前系統IO負載情況,判斷是否提交IO請求。否則,用戶線程會提交很多無效IO請求。舉個栗子:在存儲系統中,讀請求都有timeout限制,當上層請求timeout後,IO請仍然緩存在內核態的某個隊列里等待完成。

總之,不加控制的向系統提交IO請求,當超過IO負載能力後,IO請求就會緩存在IO協議棧中的隊列里,造成存儲服務質量下降。

2.2 實現用戶態IO調度器帶來的好處

當用戶線程提交的IO請求速度超過磁碟處理速度,IO請求總會被緩存在內存里等待處理。IO請求緩存在用戶空間隊列里,並不比緩存在內核空間更能提供IO處理能力。那IO請求緩存在用戶空間里仍然有如下好處:

  • 1)將當前IO 請求的統計信息吐出來,用戶線程或者客戶端根據當前IO請求堆積情況,合理調節IO規模與速度,從而充分利用磁碟IO能力;
  • 2)對不同IO請求區別對待,優先處理優先順序高的請求;
  • 3)可取消無效IO,避免浪費磁碟IO能力;

在用戶線程與系統IO-Stack之間實現一個IO調度器,合理調度IO請求,及時吐出描述當前系統IO狀況的數據,確保系統IO不會過載,而影響服務質量。

3. ScyllaDB設計用戶態IO調度器考量的問題

線程模型

由於ScyllaDB採用per-core-per-thread線程模型,使用全局IO調度器就於該模型秉承的理念不符了。所以,最好的情況是IO調度器也是每一個線程擁有一個,這樣避免跨線程傳遞message(ScyllaDB 跨線程傳遞數據採用by message方式,而非共享內存)。但是另一方面,考慮到系統IO能力有限,存在多個線程共享一個IO調度器。

在ScyllaDB中,用戶態IO調度器主要邏輯實現在IO-Queue中。

IO-Depth

還有一個重要的問題需要考慮:如何選擇合適的IO-depth, 也就是在IO不過載的情況下,如何確定能夠並行處理的IO請求數量(關於IO-depth, 也可以參考這裡)。ScyllaDB提供一個工具,在首次運行時候,需要先跑該工具,測量出合適的IO-depth,作為ScyllaDB啟動的參數之一。

IO請求優先順序

由於存在多個用戶線程共享一個IO-Queue的情況,IO調度器的設計不是按照IO-Queue調度,是按照IO請求的優先順序調度。在ScyllaDB中,IO請求被劃分為6個不同類別,每一個類別擁有自己的優先順序(優先順序通過一個正整數來表示,數值越高,優先順序越高,這個數值稱之為shares):

  • 寫commit log,shares = 100;
  • 刷memtable數據到磁碟,shares = 100;
  • 流式讀請求,shares = 20;
  • 流式寫請求, shares = 20;
  • 處理CQL請求,shares = 100;
  • Compaction動作,shares = 100;

4. ScyllaDB用戶態IO調度器實現

ScyllaDB的用戶態IO調度器要實現的目標是:合理控制IO請求的規模與速度。保障系統不會過載的情況下,在單位時間窗口內,系統中所有IO請求按照優先順序處理,相同優先順序的按FIFO原則處理。接下來看,如何達成上述目標的。

4.1 IO調度模型

IO-Queue實現了用戶態IO調度器的主要邏輯。系統啟動後會在每一個IO-Queue中註冊多種類型的PriorityClass。每次提交IO請求都會緩存在對應的PriorityClass的隊列中。 IO調度是按照PriorityClass來調度的。

具體來說,每一個PriorityClass都維護一個度量IO請求消耗的變數。每一個IO請求都有一個消耗值(cost), 當IO請求提交到內核後,該消耗值會累加到對應的PriorityClass的總消耗變數中。調度器總是選擇一個擁有最小累加消耗值的PriorityClass,將其隊列的第一個IO請求提交到內核。

也就是說,PriorityClass 總消耗值越小,越被調度器優先選擇並執行其隊列中的IO請求。IO請求提交到系統後,需要根據該IO請求在隊列中等待時間,評估本次IO請求的消耗值,並將該值累加到所在PriorityClass中。

總的流程看起來很簡單。如何評估一次IO請求的消耗值呢?繼續往下看。

4.2 IO請求消耗的評估

在此之前,我們先了解下什麼是指數衰減。

某個量的下降速度和它的值成比例,稱之為服從指數衰減。關於指數衰減,更多內容請閱讀維基百科裡的內容。ScyllaDB 的用戶態IO調度器使用指數衰減模型描述IO請求的消耗(cost)。一次IO請求從發起請求到提交的IO-Stack這段時間裡,IO請求的cost是時間的函數。

IO請求提交到調度器時候,會根據數據長度,所在PriorityClass來確定一個初始IO消耗值。直到IO請求被提交到內核,調度器會根據IO請求在調度器中等待的時間,以及初始消耗值,按照指數衰減函數來計算此次IO消耗值。

由此可見,一個IO請求等待時間越長,其消耗值衰減的月厲害,最終累加到PriorityClass上值就會少,也就是對該PriorityClass上其他請求影響減弱。

4.3 用戶態IO調度器實現細節

一次IO請求的消耗初始值與數據長度成正比,與PriorityClass的優先順序值成反比。

IO請求從提交到調度器,到請求被提交到內核過程中,其消耗值服從指數衰減。當超過衰減周期後,本次IO請求對所在PriorityClass的總消耗值影響就微乎其微了。

5. 後記

ScyllaDB 的用戶態IO調度器的實現,給存儲系統處理IO問題提供了一種思路。以往工作中,用Google的LevelDB作為存儲引擎,經常遇到IO能力在讀,寫,Compaction 之間分配不均衡,導致系統服務質量急劇下降的情況。

引用

[1]: Designing a Userspace Disk I/O Scheduler for Modern Datastores: the Scylla example (Part 2)

[2]: Designing a Userspace Disk I/O Scheduler for Modern Datastores: the Scylla example (Part 1)

[3]: zh.wikipedia.org/wiki/%

[4]: Fio壓測工具和io隊列深度理解和誤區

本文屬於我翻閱ScyllaDB代碼時候隨手筆記的一部分,經過整理分享出來,希望有更多人認識ScyllaDB, 使用ScyllaDB。轉載請註明出處。

推薦閱讀:

又拍雲停滯很久了怎麼回事?
阿里雲在4月1日提出的Matrix+計劃有沒有科學依據?
一句話說出你對雲計算的理解?
如果你有機會發射一顆衛星,你會用它來做什麼?
在青雲(QingCloud)工作是怎樣一種體驗?

TAG:云计算 | NoSQL | Cassandra |