拆開Ceph看隊列和線程


作者:吳香偉 發表於 2017/01/08
版權聲明:可以任意轉載,轉載時務必以超鏈接形式標明文章原始出處和作者信息以及版權聲明


我上小學時家離學校很遠,家在某某山腳,學校在鎮里。每周回家一趟,周五放學后回家,周日帶着一星期吃的梅干菜回學校。從家到學校有5公里山路,現在覺得5公里不算長,騎個自行車只要一溜煙的功夫,但那時還小覺得很遠。從學校到家的路上零零散散有幾個路亭,這些路亭有的建在岔路口,有的建在木橋邊,還有的建在轉彎處。不同位置的亭子有不同好處,岔路口的宜送別,小橋邊的方便在亭里歇個腳后再到橋底洗把臉,轉彎處的路亭告訴你前面還有人家。除此之外,路亭還有很多其它功能,下雨天可以供人避雨,艷陽天可以供人乘涼,到了過年過節時還可以供人拜祭各路神仙。對我來說印象最深刻的是期末時在路亭里和小伙伴們相互修改成績單,開學時相互填家長評語,其樂無窮。

從用戶發起請求到數據落盤,Ceph也有很長的IO路徑。在這條路徑上也同樣散落着功能各異的亭子(隊列),梅干菜從一個路亭到下一個路亭靠的是兩只小腳丫,IO從一個隊列到另一個隊列靠的是線程(池)。沒錯,本文要講的就是路亭和腳夫的故事。不對是隊列和線程。

隊列和線程的關系

隊列和線程在一起,通常稱為生產者和消費者模式。生產者向隊列添加元素,消費者從隊列提取元素。一個隊列可以同時擁有多個生產者和多個消費者。

圖中的Tgt是改造過的與原生版本略有不同。

Ceph客戶端隊列

Pending隊列

一個Tgt進程有多個Pending隊列,一個隊列負責來自一個Pool的請求。一個Pending隊列只有一個生產者,所有隊列共享同一個生產者,這個生產者就是Tgt主線程。Tgt主線程負責接收網絡報文,並依據iSCSI協議將報文轉換為iSCSI請求。因為要負責接收來自所有Lun的請求,所以主線程並不空閑,如果繼續負責請求的處理,必然導致后面的請求不能被及時接收,進而降低Initiator發送請求的速度。網絡本來就慢,再延遲接收,將導致性能大大的低。

此時Pending隊列起到了緩沖的作用,讓主線程只負責請求的接收過程,接收到的請求不用立即處理先緩存到隊列,縮短主線程的執行路徑從而解決網絡請求不能被及時接收的問題。從另一方面來說,Pending隊列解耦請求的接收過程和處理過程,將這兩個過程分別交給隊列的生產者和消費者。

Ok,我們Get到了隊列的第一個作用,緩沖,第一個附帶作用,解耦。一般在設計隊列時考慮緩沖,在分析代碼時看解耦。因為有100種解耦方法,隊列只是其中之一,解耦並不是設計隊列的充分條件。

PointerWq隊列

開發者調用Librbd API讀寫塊設備時,請求入PointerWq隊列后立即返回給調用者。話音未落,我似乎又Get到了隊列的第3個作用:實現異步調用。調用者將請求以及請求結果處理函數一並提交給Rbd層,Rbd層完成請求后回調請求結果處理函數,以此實現異步調用。

PointerWq隊列隸屬於ImageCtx類,代表一個Image存儲塊,不論讀請求還是寫請求都入同個隊列。Bs-worker線程池,Pending隊列的消費者,PointerWq隊列的生產者,主要工作是將一個Pool請求隊列分裂成若干個Image請求隊列。

Out_q隊列

從請求的粒度來看,Image請求在Ceph客戶端中有下面幾種形態:Image請求、Object請求、Op以及MOsdOp消息。Image請求只關注塊設備中的一段區域,一段塊設備區域可能對應於多個Object,這些Object可能屬於不同PG不同Osd節點。一個Out_q隊列對應一個Osd節點,隸屬於一個連接,一個OSDSession。

Out_q是個優先級隊列(第4個功能),不過對優先級的處理比較粗暴,只有最高優先級的請求全部出隊列后才允許次高優先級的請求出隊列。Ceph社區中曾有人建議通過請求的優先級來實現QoS,這顯然是行不通的,因為特別容易導致低優先級用戶餓死。

作為Out_q隊列的生產者,Rbd_op_threads線程池的壓力不小,一方面默認情況下池中只有一個線程(不知道是怎么考慮的)但要處理所有Image請求;另一方面它處理的邏輯最多,包括在Rbd層把Image請求拆分成Object請求(還要考慮Rbd緩存),在Rados層計算每個Object的目標Osd,以及回調函數的層層封裝。雖然事情很多,但總體而言還是計算密集型不存在等待問題。

對Out_q隊列的消費者Worker,本文關注的是AsyncMessenger的實現,SimpleMessenger會不一樣。Messenger隸屬於RadosClient,一個RadosClient實例擁有一個Messenger實例。老版本的SimpleMessenger會為每個連接都創建一個讀線程和一個寫線程。原生Tgt中為每個存儲塊創建一個RadosClient對象,一個RadosClient將與一個Osd建立一個連接,一個連接將有兩個線程。粗略想想線程數目,心里有點發毛。

AsyncMessenger將工作線程Worker和Messenger解耦,不論有多少個RadosClient實例總線程數目都固定不變。不過和給定Osd節點的連接數還是會隨着RadosClient實例的增加遞增。一個Worker會同時處理多個連接,主要職責是從隊列提取消息發送給Osd進程。

Ceph Osd隊列

Osd進程從端口接收數據和Ceph客戶端發送數據一樣都由Worker線程完成。不同的是,Osd進程是服務端要監聽端口。對監聽Fd的Worker(處理連接打開和關閉)和屬於該監聽Fd連接(處理請求接收和響應發送)的Worker是同一個線程。

Osd接收到請求后根據不同的請求類型有兩種處理方式:一種是Fast dispatch,要么直接處理掉要么入ShardedOp隊列。另一種是入Dispatch隊列。客戶端讀寫請求走Fast dispatch路徑,命令或者更新OsdMap版本的請求入Dispatch隊列。

Mqueue分發隊列

Mqueue是個優先級隊列。

(待續)

參考資料

源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM