QEMU 和 Ceph 的 I/O QoS 實現

目錄

令牌桶算法

令牌桶算法是一個非常老牌的 I/O 控制算法,在網絡、存儲 I/O 上都有着廣泛的應用。即:一個固定容量的桶裝着一定數量的令牌,桶的容量即令牌數量上限。桶裏的令牌每隔固定間隔補充一個,直到桶被裝滿。一個 IO 請求將消耗一個令牌,如果桶裏有令牌,則該 IO 請求消耗令牌後放行,反之則無法放行。對於限制 IO 請求 bps,只需讓一個 IO 請求消耗 M 個令牌即可,N 即爲此 IO 請求的字節數。

在這裏插入圖片描述

令牌桶算法可以達到以下效果:

  • 令牌桶算法可以通過控制令牌補充速率來控制處理 IO 請求的速率;
  • 令牌桶算法允許一定程度的突發,只要桶裏的令牌沒有耗盡,IO 請求即可立即消耗令牌並放行,這段時間內 IO 請求處理速率將大於令牌補充速率,令牌補充速率實際爲平均處理速率;
  • 令牌桶算法無法控制突發速率上限和突發時長,突發時長由實際 IO 請求速率決定,若實際 IO 請求大於令牌補充速率且速率恆定,則:突發時長 = 令牌桶容量 / (實際 IO 請求速率 - 令牌補充速率)

在令牌桶算法的描述中,有一個條件是無強制約束的,那就是在桶裏的令牌耗盡時,無法放行的 IO 請求該怎麼處理。對於處理網絡層報文,實際無外乎三種方式:

  • 第一種是直接丟棄(Traffic Policing,流量監管)。
  • 第二種則是排隊等待直到令牌桶完成所缺失數量令牌補充後再消耗令牌放行(traffic shaping,流量整形)。
  • 第三種爲前兩者的折中,設定有限長度隊列,在隊列已滿時丟棄,否則遵循第二種處理方式。當然對於不可隨意丟棄的 IO 請求,處理方式一般爲第二種。

此外,一般在實際的實現中,難以做到嚴格按照固定時間間隔一次補充一個令牌。可考慮的替代方案一般爲,加大補充令牌的時間間隔,減少補充次數,但一次補充多個令牌,單次補充令牌的個數與時間間隔成正比。

對於令牌桶算法,還可以考慮一種極端的情況。令牌桶算法支持突發的能力是由令牌桶的容量決定的,以限制 IO 請求 iops 爲例,假設我們將令牌桶容量僅設爲 1,此時令牌桶算法支持突發的能力也就不復存在了,IO 請求速率上限即被嚴格控制爲令牌補充速率。

漏桶算法

漏桶算法有兩種類型的定義:leaky bucket as a meter 和 leaky bucket as a queue。

leaky bucket as a meter:同樣以限制 IO 請求iops爲例,替換上述的網絡層報文,我們可以理解如下:一個桶,容量固定。桶以固定的速率漏水,除非桶爲空。一個 IO 請求將往桶裏增加固定量的水,假如增加的水量將導致桶裏的水溢出,則該 IO 請求無法放行,反之則放行。

再把令牌桶的理解放在這裏對比下:一個固定容量的桶裝着一定數量的令牌,桶的容量即令牌數量上限。桶裏的令牌每隔固定間隔補充一個,直到桶被裝滿。一個 IO 請求將消耗一個令牌,如果桶裏有令牌,則該 IO 請求消耗令牌後放行,反之則無法放行。

實際上,把漏水換成補充令牌,把加水換成消耗令牌,再細細品讀,我們可以發現,這兩段描述的含義就是一樣的,僅僅是角度不一樣而已。所以 leaky bucket as a meter 與 token bucket 可認爲是等價的。

leaky bucket as a queue:漏桶實際爲一個有限長隊列。當一個報文到達時,假如隊列未滿,則入隊等待,反之則被丟棄。在隊列不爲空時,每個固定時間間隔處理一個報文。

在這裏插入圖片描述
雖然描述更爲簡單,dan 可以看到 leaky bucket as a queue 比 leaky bucket as a meter 的要求要嚴格得多,leaky bucket as a queue 可以達到以下效果:

  • 可以通過控制處理時間間隔來嚴格控制速率上限;
  • 不支持任何程度的突發;
  • 對超出限定速率的報文可能做入隊等待處理,也可能做直接丟棄處理,當隊列長度設置爲足夠小,設置爲 0 時,可認爲等同於全部做直接丟棄處理(Traffic Policing),而隊列長度設置爲足夠大時,可認爲等同於全部做入隊等待處理(Traffic Shaping)。

如此一來,我們可以看到 leaky bucket as a queue 實際效果與令牌桶容量爲 1 的令牌桶算法是一致的,也就是說 leaky bucket as a queue 其實可視爲令牌桶算法的一種特例(不支持突發)。

前端 QoS:通過 QEMU 的塊設備 IO 限速機制進行限速

QEMU 早在 1.1 版本就已支持塊設備的 IO 限速,提供 6 個配置項,可對上述 6 種場景分別進行速率上限設置。在 1.7 版本對塊設備 IO 限速增加了支持突發的功能,以總 iops 場景爲例:支持設置可突發的總 iops 數量。在 2.6 版本對支持突發的功能進行了完善,可控制突發速率和時長。
在這裏插入圖片描述
QEMU 的塊設備 IO 限速機制主要是通過漏桶算法實現。在 2.6 版本中不僅支持突發,可支持控制突發速率和時長。

在這裏插入圖片描述
QEMU 同樣使用了多個桶,以對 6 種場景獨立進行 QoS 限速。

QEMU 通過大桶和突發小桶的設計,支持了對突發速率和突發時長的控制。有突發流量到來時,限速分爲 3 個階段:

  1. 首先是突發小桶未滿時,未做速率限制,但此突發小桶的容量僅設置爲突發速率值的 1/10,所以此階段所經歷的時間特別短,效果可忽略不計;
  2. 其次是突發小桶已滿而大桶未滿的階段,速率即被限制爲突發速率,由於大桶在不斷地以基本速率漏水,所以實際的突發時長要大於設置的突發時長,此階段的實際突發時長爲:實際突發時長 = 大桶容量 / (突發速率 - 基本速率上限),而 大桶容量 = 突發速率 * 設置突發時長
  3. 最後是大桶已滿的階段,此時速率就被限制爲基本速率上限了。

在不考慮突發的情況下,從算法效果來看,QEMU 實現的漏桶算法實際爲桶容量很小的令牌桶算法,當然也因爲桶容量足夠小,所以基本可視爲我們狹義理解上的漏桶算法。

與 Librbd 固定時間間隔補充令牌不同,QEMU 在處理每個請求時同步執行漏水操作,每秒執行漏水操作的次數實際與每秒 IO 請求數量相等,漏水頻率一般遠大於 Librbd 補充令牌的頻率。這也形成了一個特點,在 QoS 限速情況下,QEMU 處理 IO 請求的時間點分佈比較均勻,而 Librbd 則相對集中在補充令牌的時間點上。同時 IO 請求延遲的分佈特點也與 Librbd 有明顯的不同。

後端 QoS:通過 librbd 的鏡像 IO 限速機制進行限速

Ceph 在 13.2.0 版本(M 版)支持對 RBD 鏡像的 IO 限速,此版本僅支持總 iops 場景的限速,且支持突發,支持配置突發速率,但不可控制突發時長(實際相當於突發時長設置爲 1 秒且無法修改)。在 14.2.0 版本(N 版)增加了對讀 iops、寫 iops、總 bps、讀 bps、寫 bps 這 5 種場景的限速支持,對突發的支持效果保持不變。

在這裏插入圖片描述

Ceph Librbd 的鏡像 IO 限速機制使用的是令牌桶算法。所以,Librbd 的限速機制支持突發,支持配置突發速率,但不支持控制突發時長,這與令牌桶算法定義描述的效果近乎一致。而從實際的實現來看,Librbd 的令牌桶算法實現也的確與定義比較匹配。

在這裏插入圖片描述
Librbd 使用了 6 個令牌桶,所以可對 6 種場景獨立進行 QoS 限速,若這些場景的 QoS 限速全部配置啓用,那麼一個 I/O 請求需在每一個令牌桶消耗相應數目的令牌後纔可被放行(當然,寫請求在限制讀速率的桶無需消耗令牌,反之亦然)。

令牌桶的容量由基本速率上限值或突發速率值決定,而且突發速率值的唯一作用就是用於配置令牌桶容量。從上面的內容可知,令牌桶算法的效果分析來看,只要令牌桶容量不是足夠小,該令牌桶算法即支持突發。所以即使未設置突發速率,令牌桶容量由於爲基本速率值,實際也是支持突發的:突發時長 = 基本速率值 / (實際 IO 請求速率 - 基本速率)

此外,令牌桶算法本身無法控制突發速率上限,所以設置所謂的突發速率其實並沒有對真正對速率上限進行限制,只是通過增大令牌桶容量而客觀地增大了突發時長:突發時長 = 突發速率值 / (實際 IO 請求速率 - 基本速率)

此處以限制 iops 來舉個實際例子,基本速率設置爲 1000iops,突發速率設置爲 2000iops,假如實際 IO 請求速率恰好爲 2000iops,那麼突發時長實際就支持 2 秒。而且實際 IO 請求速率並沒有得到限制,假如達到了 5000iops,那麼實際突發時長僅爲 0.5 秒,即 0.5 秒後,速率就被穩定限制在 1000iops。可見 Librbd 的突發速率概念還是很容易給人帶來誤解的,實際應理解爲可突發的 I/O 請求量。

Librbd 的令牌桶算法實現中,加大了補充令牌的時間間隔,一次補充多個令牌,此方式有助於減小算法的運行開銷。在令牌補充的一瞬間到令牌消耗完這段時間內,IO 請求由於獲得了足夠的令牌數量,可被快速放行,而一旦令牌耗盡,超過 QoS 限速的請求則陷入排隊等待的狀態,等待時間即爲令牌補充的時間間隔,甚至更多。

總結

Librbd 的限速算法與 QEMU 相比有兩大不同,一是 Librbd 無法像 QEMU 一樣控制突發速率上限和突發時長,二是 Librbd 補充令牌的方式和 QEMU 漏水的方式不同,前者導致請求被處理的時間點較爲集中,類似於一批一批地處理,後者則使請求被處理的時間點分佈均勻,類似於一個一個地處理。

從測試結果上可以看到,同等條件下,IO 請求平均時延接近於相等,而 QEMU 的限速機制讓每個 IO 請求的延遲都接近於平均時延,Librbd 的限速機制則使大部分 IO 請求有較低的延遲,但有一定比例的 IO 請求延遲很高,接近於補充令牌的時間間隔。

參考文章

https://mp.weixin.qq.com/s/rqVQcF6ucgQDdrImjsSYdA

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章