由於公司的分佈式存儲產品(基於Ceph)需要提供QoS特性,之前也相關經驗,所以打算先分析QEMU QoS的特性及其實現原理,然後基於此,給出Librbd QoS的定義,內容如下:
由於GNU Linux
系統的cgroup
不支持網絡設備(nfs
,ceph
)的資源控制,爲提供一套通用的I/O
流控機制。QEMU
實現了一個獨立的流控
模塊來控制磁盤I/O
操作,該模塊能實現磁盤TPS
、OPS
及潮汐式
流量控制,具體特性如下:
QEMU QoS
特性
磁盤I/O
的兩個方面: 每秒的數據量(TPS
)以及每秒的I/O
操作(OPS
):
QEMU
能分別爲TPS
和OPS
提供全局配置或者針對讀寫的獨立配置。- 全局配置和針對讀寫的差異配置不能同時使用
- 支持同時控制
TPS
和OPS
- 支持
潮汐
式I/O
流量控制 - 支持
I/O
大小控制 - 支持基於磁盤組的流控控制
QEMU
默認不開啓流量控制
全局配置
及讀寫差異配置
QEMU
針對TPS
及OPS
分別提供全局配置及讀寫配置。全局配置(*-total
)同時控制讀寫I/O
操作,而讀寫配置(*-read
, *-write
)則通過設置不同的參數分別控制讀寫I/O
操作。
全局配置和讀寫配置不能同時使用
潮汐式流量控制
除了上述標準的TPS
及OPS
控制,QEMU
還支持潮汐式流控,允許磁盤I/O
在指定長度的時間(*-total-max-length
)內超過標準配置,出現I/O
波峯(*-total-max
),這在計劃內的流量高峯期很有用(如:系統啓動,服務啓動)。
控制I/O
大小
在執行OPS
控制時,通常不考慮I/O
的大小,所有的I/O
都同等對待,從TPS
時序圖上看,就是一個波浪形的流量線。QEMU
支持配置(io-size
)來平滑,磁盤流量。大於io-size
的請求將會正確的分解爲多個I/O
,而小於io-size
的請求會被當做一個I/O
。
磁盤組流控
前述的各種配置都是針對單個磁盤的。此外,QEMU
還提供了磁盤組流控功能,加入相同流控配置組的所有磁盤共享相同的限制。默認情況下,每個磁盤設備都會創建一個以自身名字爲組名的配置組,同時一個設備只能加入一個配置組。
QEMU QoS
原理
leaky bucket algorithm
是網絡世界中流量塑型(Traffic Shaping
)或速率限制(Rate Limiting
)時常用的一種算法,它的主要目的是控制數據注入到網絡的速率,平滑網絡上的突發流量。通過漏桶算法,突發流量可以被整形(Shaping
)以便爲網絡提供一個穩定的流量。
QEMU
中也採用漏桶
算法實現I/O
限制。這就像是一個漏水的桶,倒入桶中的水就是要執行的I/O
, 桶滿了,I/O
就不能被加入,被堵塞了。舉下面的例子來說明:
iops-total = 100
iops-total-max = 2000
iops-total-max-length = 60
- 水從桶中漏走的速度是 100 IOPS
- 往桶中倒水的速度是 2000 IOPS
- 桶的大小是 2000 * 60 = 12000
- 如果
iops-total-max
沒有設置,桶的大小就是 100 * 60 = 600
開始的時候桶是空的,所以可以以 2000 IOPS的速度往桶裏面加入I/O
,直到桶滿。桶滿後,就只能以漏水的速度往桶裏面加入I/O
,也就是 100 IOPS。如果之後加水的速度小於漏水的速度,到某個時刻桶就會空,這個時候就又可以以 2000 IOPS的速度往桶裏面加入I/O
。
需要注意的是,桶是一直在漏水的,所以桶加滿水的時間通常是大於60秒。另外,如果加水的速度小於2000 IOPS,桶加滿水的時間也是大於60秒。
QEMU QoS
實現
QEMU
的I/O
控制由塊層實現,流控模塊實現包含在Throttle.h/.c
,Throttle-groups.h/.c
文件中,完整的塊設備I/O
限制,還依賴於協程coroutine
及定時器(timer
)。
QEMU 協程
協程(coroutine
)是一種實現堆棧切換的機制,通常用來實現用戶態線程間的協作。針對不同的系統,QEMU
提供了多種協程實現方式(gthread
,signalstack
,ucontext
)等,代碼實現在coroutine-*
文件中,QEMU
中主要提供了三個接口:qemu_coroutine_create
,qemu_coroutine_enter
,qemu_coroutine_yield
來使用協程
qemu_coroutine_create
創建協程qemu_coroutine_enter
執行協程qemu_coroutine_yield
阻塞,等待退出
其中,qemu_coroutine_yield
可以不主動調用,QEMU
執行完用戶代碼後,會主動執行qemu_coroutine_switch(co, co->caller, COROUTINE_TERMINATE)
在(coroutine_trampoline
)函數裏。如果調用了qemu_coroutine_yield
,如在異步IO中,此協程會被掛起,等到I/O
完成時,在回調(callback
)中調用qemu_coroutine_enter
進入該協程運行,剛好該協程運行的是COROUTINE_TERMINATE
那句,從而完成了協程的退出。
這裏不打算解析具體代碼,只通過圖表的形式,以ucontext
實現爲例,展示QEMU coroutine
的工作原理:
用戶代碼調用
qemu_coroutine_create
創建協程
1.1 調用qemu_coroutine_new
創建協程,返回coroutine
對象
1.1.1 切換到coroutine_trampoline
執行,設置協程環境信息(self->env
)
1.1.2siglongjmp
跳轉到qemu_coroutine_new
的sigsetjmp
處繼續執行
1.1.3 返回協程對象用戶代碼調用
qemu_coroutine_enter
執行協程
2.1 調用qemu_coroutine_switch
,執行堆棧切換
2.1.1siglongjmp
跳轉到coroutine_trampline
的sigsetjmp
處繼續執行
2.2 執行co->entry
用戶代碼
2.3 調用qemu_coroutine_switch
,執行堆棧切換,回到之前的qemu_coroutine_switch
的sigsetjmp
處繼續執行
2.4 返回到qemu_coroutine_enter
,操作完成,刪除當前協程
I/O
控制
QEMU
通過協程來執行I/O
請求,在Block-backend.c
文件的blk_aio_prwv
函數中調用前述方法完成協程的創建和執行,協程入口函數爲:blk_aio_write_entry
,流程圖如下:
在I/O
的後續處理過程中Exceed Limits
部分,由QEMU Throttling
模塊實現。在設備初始化過程中,如果開啓了I/O throttling
, 就會在blockdev.c
的blockdev_init
方法中初始化throttling
配置,設置讀寫timer
並與設備關聯,類圖如下:
要點如下:
- 每個設備關聯一個
ThrottleGroup
(如果開啓了I/O
流量限制), 所有的組由全局變量throttle_groups
管理 - 每個
ThrottleConfig
可包含達6個I/O
限制配置(LeakyBucket
) - 每個設備分別關聯讀寫兩個定時器(
timer
),當超過I/O
限制時,I/O
協程被阻塞,定時器被設置並添加到event loop
,定時器超時後,會重新進入協程繼續I/O
處理
I/O throttling
模塊內部的處理邏輯如下:
Ceph Librbd QoS
定義
默認情況下,Ceph Rbd
並不提供QoS
功能,然而在Ceph Cluster
集羣存儲能力一定的情況下,爲避免某個Rbd image
的突發流動對其他的image
帶來影響,限制某個Rbd
設備的I/O
流量,就很有必要。
結合上述對QEMU QoS
的特性及原理分析,我們將librbd QoS
定義如下:
- 提供基於
TPS
和OPS
的I/O
限速功能 - 爲
TPS
和OPS
提供全局配置或者針對讀寫的獨立配置(兩種配置不能同時設置) - 支持
潮汐
式I/O
流量控制 - 支持
I/O
大小控制