QEMU QoS特性及原理分析和Librbd QoS定義

由於公司的分佈式存儲產品(基於Ceph)需要提供QoS特性,之前也相關經驗,所以打算先分析QEMU QoS的特性及其實現原理,然後基於此,給出Librbd QoS的定義,內容如下:

由於GNU Linux系統的cgroup不支持網絡設備(nfs,ceph)的資源控制,爲提供一套通用的I/O流控機制。QEMU實現了一個獨立的流控模塊來控制磁盤I/O操作,該模塊能實現磁盤TPSOPS潮汐式流量控制,具體特性如下:

QEMU QoS 特性

磁盤I/O的兩個方面: 每秒的數據量(TPS)以及每秒的I/O操作(OPS):

  • QEMU能分別爲TPSOPS提供全局配置或者針對讀寫的獨立配置
  • 全局配置針對讀寫的差異配置不能同時使用
  • 支持同時控制TPSOPS
  • 支持潮汐I/O流量控制
  • 支持I/O大小控制
  • 支持基於磁盤組的流控控制
  • QEMU默認不開啓流量控制

全局配置讀寫差異配置

QEMU針對TPSOPS分別提供全局配置及讀寫配置。全局配置(*-total)同時控制讀寫I/O操作,而讀寫配置(*-read, *-write)則通過設置不同的參數分別控制讀寫I/O操作。

全局配置和讀寫配置不能同時使用

潮汐式流量控制

除了上述標準的TPSOPS控制,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實現

QEMUI/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的工作原理:

  1. 用戶代碼調用qemu_coroutine_create創建協程
    1.1 調用qemu_coroutine_new創建協程,返回coroutine對象
    1.1.1 切換到coroutine_trampoline執行,設置協程環境信息(self->env)
    1.1.2 siglongjmp跳轉到qemu_coroutine_newsigsetjmp處繼續執行
    1.1.3 返回協程對象

  2. 用戶代碼調用qemu_coroutine_enter執行協程
    2.1 調用qemu_coroutine_switch,執行堆棧切換
    2.1.1 siglongjmp跳轉到coroutine_tramplinesigsetjmp處繼續執行
    2.2 執行co->entry用戶代碼
    2.3 調用qemu_coroutine_switch,執行堆棧切換,回到之前的qemu_coroutine_switchsigsetjmp處繼續執行
    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.cblockdev_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定義如下:

  • 提供基於TPSOPSI/O限速功能
  • TPSOPS提供全局配置或者針對讀寫的獨立配置(兩種配置不能同時設置)
  • 支持潮汐I/O流量控制
  • 支持I/O大小控制
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章