Ceph Network Architecture 研究(一)

此篇文章主要研究目前很熱門的 scale out storage 軟體 ceph 的網路架構。
由於 scale-out 的特性,不同節點彼此之間需要透過網路來交換資料,所以 ceph
這邊提供了 public network 以及 cluster network 兩種不同的用途的網路架構。

其中 cluster network 是用來傳輸資料用的,當各節點有資料要同步時,流量都是走這個網路來交換,
public network則是處理剩下的流量,譬如外面的資料讀取(read/write)等。

因此接下來將針對 Ceph 內部的網路架構進行研究,並且最後選擇一種類型進行更進一步的研究。

Environment

  • 本文所觀看的架構以及程式碼都基於 ceph 12.0.0
  • 所有網路的程式碼都放在 /src/msg/ 內部資料夾內。
  • 可以透過修改 ceph.conf中的 ms_type 來改變當前 ceph 要使用何種網路架構來連線。

Introduction

在此版本中, ceph 內部的網路架構大致上可以分成三種來使用

  • Simple
  • Async
  • Xio

若要更細微去分的話,則 Async 又可以細分成三成類型,分別是

  • Posix
  • DPDK
  • RDMA

接下來會介紹一下這些類型的概念。

Simple

Simple顧名思義就是簡單,其網路架構也是非常的簡單,是 ceph 最早期的設計
每一對節點之間都會有一條 connection,而每一條 connection 又會產生兩個 thread,分別負責send/write的行為。
所以當 connection 數量一多的時候,會產生非常多的 thread,然後每個 thread 都各自在自己的迴圈內去處理自己的事情。這種網路程式設計的方法到現今幾乎都被 event-based 的方法給取代,當有事件發生時再來處理,而不是開一個 thread 在那邊癡癡的等待封包處理。
若要使用這種架構,則將 ms_type 修改為 simple 即可。

Xio

Xio比較不算是原生的功能,其用到了第三方的library Accelio XIO
Accelio XIO是一套提供穩定,高速網路資料交換的函式庫,除了支援常見的 Ethernet Network 外,也有支援 RDMA, 所以現在網路上看到很多 ceph 關於RDMA的效能測量都是基於使用 Xio作為其網路傳輸的實現。
然而 ceph 後來也沒有在維護使用xio作為其網路傳輸的一部份,主要還是因為要引入第三方函式庫,不是直接使用 ceph本身的架構,同時若有任何bug出現,除非等待xio修復,否則開發人員還要再花時間理解一套第三方函式庫,並且嘗試解決問題,這在整理維護成本上是不容忽視的。
若要使用這種架構,則將 ms_type 修改為 xio 即可。

Async

相對於 simple 採用兩條 thread 專門負責收送動作的做法,Async則是採用 event-driven的方式來處理請求,整體設計上會有一個thread pool,會有一個固定數量(還是可透過API動態調整)的 thread
然後將每一條 connection 分配到一個 thread 身上,然後透過監聽該connection的狀況,當底層觸發了或是的事件時,更精準的應該是講 read/write,就會呼叫對應的函式來處理相關的行為。
此種設計可大大減少系統上開啟的 thread 數量,減少系統的消耗。
在此設計下, ceph 又提供了三種模式,分別是 POSIX, DPDK 以及 RDMA
這三者的切換方式分別是將ms_type修改為

  • async
  • async+dpdk
  • async+rmda
POSIX

POSIX代表的就是走 kernel 大家熟悉的event機制,譬如select, epoll 以及 kqueue.
同時此類型也是 async的預設類型,因為此類型完全不需要任何硬體的幫忙,完全是靠software的方式就可以完成的,主要是看 kernel 本身的支援程度來決定實際上會呼叫出哪一種實現方式來使用。

DPDK

Data Plane Development Kit(DPDK)是一套 intel所主導的技術,基本上就是使用CPU換取效能的機制,藉由此機制,user-space的程式可以略過 kernel 直接跟硬體溝通,這部分採用的是polling的方式,藉由不停地詢問來加強整理的效能,然而也會因為一直不停的polling使得cpu使用率提升。
然而此技術只要是x86的 CPU 基本上都支援,所以在使用上其實可以說是非常的廣泛,很容易被支援,不太會有被特定硬體綁住限制的機會。

RDMA

RDMA代表的是遠方記憶體存取,是一種擁有low latency, low cpu consumption, kernel by pass等特性的傳輸方法,藉由跳過kernel-space的方式,讓整體資料流量直接透過網卡去處理,同時也可以直接針對遠方的記憶體進行修改而不會讓遠方的CPU察覺這件事情。
一般來說支援的網路底層有 Infiniband 以及 Ethernet,這部分由於封包會忽略 kernel space,因此資料在 internet 上傳遞勢必要符合當前廣泛的格式,譬如 Ethernet,因此這邊會採用 ROCE 的方式來處理封包之間的標頭問題。
目前 ceph 上面已經可以運行 RDMA,不過根據開發者在 Mail 中的說法,目前還是在尋求穩定性為主,效能上還沒有達到最佳化的部分,因此使用起來與 POSIX w/ TCP/IP 在各方面都不會有太明顯的提升。

在看完這些網路底層的實現後,接下來要來探討Network是如何提供介面給其他元件,如OSD,Mon等使用的。

Architecture

整個網路最基礎的架構程式碼都放在 /src/msg/ 裡面,排除三種類型的資料夾外,大致上就是下列四種類型

  • Message
  • Connection
  • Messenger
  • Dispatcher

而剛剛上述提到的那些種類,其實就是繼承這些基本架構,並且實現了每個介面的功能而已。
因此接下來會比較偏向概念性的去分析這四種概念的用途。

Messege

此物件主要用來定義封包的格式,所有 ceph node之間的封包傳送都必須要參照此格式,不過對於應用層(osd,mon等)不需要擔心這邊,這邊是由網路層去負責包裝跟解析的

Connection

代表任意兩個 ceph node 之間的連線,彼此之間可以傳送/接收封包。

Messenger

此物件用來管理連線,一種Messenger可以管理多條連線,目前在 osd 的使用中,是採用一種類型的連線使用一個 Messenger,而底下可以有很多條connection
舉例來說,今天有兩種連線類型分別代表 heartbeat public 以及 heartbeat cluster,且環境中有 三台osd,所以於 mesh 的架構中,總共會有六條連線,(每台 osd 彼此互連,且都有兩種類型的連線)。
heartbeat publicmessenger 會採用 public network去建立 connection,而在 heartbeat clustermessenger 則是會採用 cluster network 去建立 connection。
這邊的範例剛好是使用不同網路類型的 messenger,實際上也可以是不同用途的,譬如用來傳遞 heartbeat,用來傳送 control message 或是用來傳送 data message*之類的。
這邊的架構如附圖

可參考 OSD 實際創造這些 messenger 的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Messenger *ms_public = Messenger::create(g_ceph_context, public_msgr_type,
entity_name_t::OSD(whoami), "client",
getpid(),
Messenger::HAS_HEAVY_TRAFFIC |
Messenger::HAS_MANY_CONNECTIONS);
Messenger *ms_cluster = Messenger::create(g_ceph_context, cluster_msgr_type,
entity_name_t::OSD(whoami), "cluster",
getpid(),
Messenger::HAS_HEAVY_TRAFFIC |
Messenger::HAS_MANY_CONNECTIONS);
Messenger *ms_hb_back_client = Messenger::create(g_ceph_context, cluster_msgr_type,
entity_name_t::OSD(whoami), "hb_back_client",
getpid(), Messenger::HEARTBEAT);
Messenger *ms_hb_front_client = Messenger::create(g_ceph_context, public_msgr_type,
entity_name_t::OSD(whoami), "hb_front_client",
getpid(), Messenger::HEARTBEAT);
Messenger *ms_hb_back_server = Messenger::create(g_ceph_context, cluster_msgr_type,
entity_name_t::OSD(whoami), "hb_back_server",
getpid(), Messenger::HEARTBEAT);
Messenger *ms_hb_front_server = Messenger::create(g_ceph_context, public_msgr_type,
entity_name_t::OSD(whoami), "hb_front_server",
getpid(), Messenger::HEARTBEAT);
Messenger *ms_objecter = Messenger::create(g_ceph_context, public_msgr_type,
entity_name_t::OSD(whoami), "ms_objecter",
getpid(), 0);

Dispatcher

Dispatcher 這邊的概念簡單來說就是當接收到封包後,要怎麼處理,每個應用層(OSD/MON.等)創建 Messenger 後,要向其註冊 dispatcher,這行為解讀成,當該 messenger 內的 connection 有從對方收到訊息後,所要執行的對應 function
function 原型內可以判別是由哪一條 connection所觸發的。

a若要註冊該 dispatcher,可參考 OSD 實際程式碼如下

1
2
3
4
5
6
7
client_messenger->add_dispatcher_head(this);
cluster_messenger->add_dispatcher_head(this);
hb_front_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_back_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_front_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_back_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);

最後做一個簡單總結,每個應用層可以自由創建 messenger,並且向其註冊對應的 dispatcher,同時使用該 messenger去管理該類型的多條連線。


至於底層是如何連線,封包如何收送,訊息如何解析、封裝,都些都是 Networking 本身的事情,就是本文開頭提到的那幾種方式去實作。

因此接下來的文章,將探討 Async 這類型的傳輸方式是如何將上述的概念給實作的。