可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

一 簡介

用戶對超高併發、超大規模計算等需求推動了存儲硬件技術的不斷髮展,存儲集羣的性能越來越好,延時也越來越低,對整體IO路徑的性能要求也越來越高。在雲硬盤場景中,IO請求從生成到後端的存儲集羣再到返回之間的IO路徑比較複雜,虛擬化IO路徑尤其可能成爲性能瓶頸,因爲虛機內所有的IO都需要通過它下發給後端的存儲系統。我們使用了SPDK來優化虛擬化IO路徑,提出了開源未解決的SDPK熱升級和在線遷移方案,並且在高性能雲盤場景中成功應用,取得了不錯的效果,RSSD雲硬盤最高可達120萬IOPS。本文主要分享我們在這方面的一些經驗。

二 SPDK vhost的基本原理

SPDK(Storage Performance Development Kit )提供了一組用於編寫高性能、可伸縮、用戶態存儲應用程序的工具和庫,基本組成分爲用戶態、輪詢、異步、無鎖 NVMe 驅動,提供了從用戶空間應用程序直接訪問SSD的零拷貝、高度並行的訪問。

在虛擬化IO路徑中,virtio是比較常用的一種半虛擬化解決方案,而virtio底層是通過vring來通信,下面先介紹下virtio vring的基本原理,每個virtio vring 主要包含了以下幾個部分:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

desc table數組,該數組的大小等於設備的隊列深度,一般爲128。數組中每一個元素表示一個IO請求,元素中會包含指針指向保存IO數據的內存地址、IO的長度等基本信息。一般一個IO請求對應一個desc數組元素,當然也有IO涉及到多個內存頁的,那麼就需要多個desc連成鏈表來使用,未使用的desc元素會通過自身的next指針連接到free_head中,形成一個鏈表,以供後續使用。

available數組,該數組是一個循環數組,每一項表示一個desc數組的索引,當處理IO請求時,從該數組裏拿到一個索引就可以到desc數組裏面找到對應的IO請求了。

used 數組,該數組與avail類似,只不過用來表示完成的IO請求。當一個IO請求處理完成時,該請求的desc數組索引就會保存在該數組中,而前端virtio驅動得到通知後就會掃描該數據判斷是否有請求完成,如果完成就會回收該請求對應的desc數組項以便下個IO請求使用。

SPDK vhost的原理比較簡單,初始化時先由qemu的vhost驅動將以上virtio vring數組的信息發送給SPDK,然後SPDK通過不停的輪尋available數組來判斷是否有IO請求,有請求就處理,處理完後將索引添加到used數組中,並通過相應的eventfd通知virtio前端。

當SPDK收到一個IO請求時,只是指向該請求的指針,在處理時需要能直接訪問這部分內存,而指針指向的地址是qemu地址空間的,顯然不能直接使用,因此這裏需要做一些轉化。

在使用SPDK時虛機要使用大頁內存,虛機在初始化時會將大頁內存的信息發送給SPDK,SPDK會解析該信息並通過mmap映射同樣的大頁內存到自己的地址空間,這樣就實現了內存的共享,所以當SPDK拿到qemu地址空間的指針時,通過計算偏移就可以很方便的將該指針轉換到SPDK的地址空間。

由上述原理我們可以知道SPDK vhost通過共享大頁內存的方式使得IO請求可以在兩者之間快速傳遞這個過程中不需要做內存拷貝,完全是指針的傳遞,因此極大提升了IO路徑的性能。

我們對比了原先使用的qemu雲盤驅動的延時和使用了SPDK vhost之後的延時,爲了單純對比虛擬化IO路徑的性能,我們採用了收到IO後直接返回的方式:

1.單隊列(1 iodepth, 1 numjob)

qemu 網盤驅動延時:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

SPDK vhost延時:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

可見在單隊列情況下延時下降的非常明顯,平均延時由原來的130us下降到了7.3us。

2.多隊列(128 iodepth,1 numjob)

qemu 網盤驅動延時:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

SPDK vhost延時:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

多隊列時IO延時一般會比單隊列更大些,可見在多隊列場景下平均延時也由3341us下降爲1090us,下降爲原來的三分之一。

三 SPDK熱升級

在我們剛開始使用SPDK時,發現SPDK缺少一重要功能——熱升級。我們使用SPDK 並基於SPDK開發自定義的bdev設備肯定會涉及到版本升級,並且也不能100%保證SPDK進程不會crash掉,因此一旦後端SPDK重啓或者crash,前端qemu裏IO就會卡住,即使SPDK重啓後也無法恢復。

我們仔細研究了SPDK的初始化過程發現,在SPDK vhost啓動初期,qemu會下發一些配置信息,而SPDK重啓後這些配置信息都丟失了,那麼這是否意味着只要SPDK重啓後重新下發這些配置信息就能使SPDK正常工作呢?我們嘗試在qemu中添加了自動重連的機制,並且一旦自動重連完成,就會按照初始化的順序再次下發這些配置信息。開發完成後,初步測試發現確實能夠自動恢復,但隨着更嚴格的壓測發現只有在SPDK正常退出時才能恢復,而SPDK crash退出後IO還是會卡住無法恢復。從現象上看應該是部分IO沒有被處理,所以qemu端虛機一直在等待這些IO返回導致的。

通過深入研究virtio vring的機制我們發現在SPDK正常退出時,會保證所有的IO都已經處理完成並返回了才退出,也就是所在的virtio vring中是乾淨的。而在意外crash時是不能做這個保證的,意外crash時virtio vring中還有部分IO是沒有被處理的,所以在SPDK恢復後需要掃描virtio vring將未處理的請求下發下去。這個問題的複雜之處在於,virtio vring中的請求是按順序下發處理的,但實際完成的時候並不是按照下發的順序的。

假設在virtio vring的available ring中有6個IO,索引號爲1,2,3,4,5,6,SPDK按順序的依次得到這個幾個IO,並同時下發給設備處理,但實際可能請求1和4已經完成,並返回了成功了,如下圖所示,而2,3,5,6都還沒有完成。這個時候如果crash,重啓後需要將2,3,5,6這個四個IO重新下發處理,而1和4是不能再次處理的,因爲已經處理完成返回了,對應的內存也可能已經被釋放。也就是說我們無法通過簡單的掃描available ring來判斷哪些IO需要重新下發,我們需要有一塊內存來記錄virtio vring中各個請求的狀態,當重啓後能夠按照該內存中記錄的狀態來決定哪些IO是需要重新下發處理的,而且這塊內存不能因SPDK重啓而丟失,那麼顯然使用qemu進程的內存是最合適的。所以我們在qemu中針對每個virtio vring申請一塊共享內存,在初始化時發送給SPDK,SPDK在處理IO時會在該內存中記錄每個virtio vring請求的狀態,並在意外crash恢復後能利用該信息找出需要重新下發的請求。

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

四 SPDK在線遷移

SPDK vhost所提供的虛擬化IO路徑性能非常好,那麼我們有沒有可能使用該IO路徑來代替原有的虛擬化IO路徑呢?我們做了一些調研,SPDK在部分功能上並沒有現有的qemu IO路徑完善,其中尤爲重要的是在線遷移功能,該功能的缺失是我們使用SPDK vhost代替原有IO路徑的最大障礙。

SPDK在設計時更多是爲網絡存儲準備的,所以支持設備狀態的遷移,但並不支持設備上數據的在線遷移。而qemu本身是支持在線遷移的,包括設備狀態和設備上的數據的在線遷移,但在使用vhost模式時是不支持在線遷移的。主要原因是使用了vhost之後qemu只控制了設備的控制鏈路,而設備的數據鏈路已經託管給了後端的SPDK,也就是說qemu沒有設備的數據流IO路徑所以並不知道一個設備那些部分被寫入了。

在考察了現有的qemu在線遷移功能後,我們覺着這個技術難點並不是不能解決的,因此我們決定在qemu裏開發一套針對vhost存儲設備的在線遷移功能。

塊設備的在線遷移的原理比較簡單,可以分爲兩個步驟,第一個步驟將全盤數據從頭到尾拷貝到目標虛機,因爲拷貝過程時間較長,肯定會發生已經拷貝的數據又被再次寫入的情況,這個步驟中那些再次被寫髒的數據塊會在bitmap中被置位,留給第二個步驟來處理,步驟二中通過bitmap來找到那些剩餘的髒數據塊,將這些髒數據塊發送到目標端,最後會block住所有的IO,然後將剩餘的一點髒數據塊同步到目標端遷移就完成了。

SPDK的在線遷移原理上於上面是相同的,複雜之處在於qemu沒有數據的流IO路徑,所以我們在qemu中開發了一套驅動可以用來實現遷移專用的數據流IO路徑,並且通過共享內存加進程間互斥的方式在qemu和SPDK之間創建了一塊bitmap用來保存塊設備的髒頁數量。考慮到SPDK是獨立的進程可能會出現意外crash的情況,因此我們給使用的pthread mutex加上了PTHREAD_MUTEX_ROBUST特性來防止意外crash後死鎖的情況發生,整體架構如下圖所示:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

五 SPDK IO uring體驗

IO uring是內核中比較新的技術,在上游內核5.1以上才合入,該技術主要是通過用戶態和內核態共享內存的方式來優化現有的aio系列系統調用,使得提交IO不需要每次都進行系統調用,這樣減少了系統調用的開銷,從而提供了更高的性能。

SPDK在最新發布的19.04版本已經包含了支持uring的bdev,但該功能只是添加了代碼,並沒有開放出來,當然我們可以通過修改SPDK代碼來體驗該功能。

首先新版本SPDK中只是包含了io uring的代碼甚至默認都沒有開放編譯,我們需要做些修改:

1.安裝最新的liburing庫,同時修改spdk的config文件打開io uring的編譯;

2.參考其他bdev的實現,添加針對io uring設備的rpc調用,使得我們可以像創建其他bdev設備那樣創建出io uring的設備;

3.最新的liburing已經將io_uring_get_completion調用改成了io_uring_peek_cqe,並需要配合io_uring_cqe_seen使用,所以我們也要調整下SPDK中io uring的代碼實現,避免編譯時出現找不到io_uring_get_completion函數的錯誤:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

4.使用修改open調用,使用O_SYNC模式打開文件,確保我們在數據寫入返回時就落地了,並且比調用fdatasync效率更高,我們對aio bdev也做了同樣的修改,同時添加讀寫模式:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

經過上述修改spdk io uring設備就可以成功創建出來了,我們做下性能的對比:

使用aio bdev的時候:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

使用io uring bdev的時候:

可實現RSSD雲硬盤120萬IOPS的SPDK IO路徑優化實踐

可見在最高性能和延時上 io uring都有不錯的優勢,IOPS提升了約20%,延遲降低約10%。這個結果其實受到了底層硬件設備最大性能的限制,還未達到io uring的上限。

六 總結

SPDK技術的應用使得虛擬化IO路徑的性能提升不再存在瓶頸,也促使UCloud高性能雲盤產品可以更好的發揮出後端存儲的性能。當然一項技術的應用並沒有那麼順利,我們在使用SPDK的過程中也遇到了許多問題,除了上述分享的還有一些bug修復等我們也都已經提交給了SPDK社區,SPDK作爲一個快速發展迭代的項目,每個版本都會給我們帶來驚喜,裏面也有很多有意思的功能等待我們發掘並進一步運用到雲盤及其它產品性能的提升上。

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