Ceph網絡模塊(2) - AsyncMessenger數據結構分析
本文主要介紹AsyncMessenger的代碼框架結構和主要使用到的數據結構
上圖表示Ceph的AsyncMessenger模塊中各個關鍵類之間的聯繫。在AsyncMessenger模塊中用到的類主要有14個,下面逐一介紹每個類的作用,以及其中包含的主要成員變量和方法。
1、 AsyncMessenger類、SimplePolicyMessenger類和Messenger類
AsyncMessenger類、SimplePolicyMessenger類和Messenger類三者是繼承與被繼承的關係,Messenger是一個抽象的消息管理器,其主要接口在派生類AsyncMessenger中實現,SimplePolicyMessenger類則是對消息管理器的一些連接的策略進行定義的設置,AsyncMessenger中定義和實現了消息管理器的相關成員變量以及方法。
一個AsyncMessenger實例的關鍵成員變量以及類方法如下表所示(包括該類繼承的父類成員變量以及類方法)。AsyncMessenger包含一個WorkerPool對象、一個Processor實例,以及3個AsyncConnectionRef對象列表和1個ConnectionRef對象列表。
AsyncMessenger類中的成員變量:
成員變量名 | 返回值類型 | 描述 |
*pool | WorkerPool | 通過pool->get_worker()從線程池中獲取工作線程來進行工作 |
processor | Processor | Processor實例,主要用來監聽連接,綁定socket,接受連接請求等,相當於AsyncMessenger的處理中心 |
listen_sd | int | 定義的監聽套接字 |
conns | ceph::unordered_map(entity_addr_t, AsyncConnectionRef) | 地址和連接的map表,創建一個新的連接時將連接和和地址信息加入到這個map表中,在發送消息時先根據地址對這個map進行一次查找,如果找到了返回連接,如果沒有找到創建一個新的連接。 |
accepting_conns | set(AsyncConnectionRef) | 接收連接的集合,這個集合主要存放那些已經接收的連接。 |
deleted_conns | set(AsyncConnectionRef) | 已經關閉並且需要清理的連接的集合 |
local_connection | ConnectionRef | 本地連接的計數器 |
did_bind | bool | 初始值爲false,綁定地址後置爲true,stop的時候再次置爲false |
AsynsMessenger類中的成員方法:
成員方法名 | 返回值類型 | 描述 |
bind (const entity_addr_t& bind_addr) | int | 綁定套接字,具體綁定過程是由Processor的bind()函數完成的 |
start() | int | 註冊一個AsyncMessenger的實例後,啓動這個實例,具體執行過程是WokerPool的start()函數完成的。 |
wait() | void | 等待停止的信號,如果收到停止的信息後,調用Processor的stop()函數,然後將did_bind置爲false,最後刪除建立的連接 |
send_message (Message *m, const entity_inst_t& dest) | int | 加了一個鎖,然後調用_send_message(m, dest),將消息發送到目的地址 |
get_connection (const entity_inst_t& dest) | ConnectionRef | 函數用來建立連接,判定是否爲本地連接,否則再繼續查找連接是否已經存在,如果不存在再創建一個連接 |
ready() | void | 註冊的AsyncMessenger已經準備好了,啓動事件處理中心,開始工作,啓動工作線程 |
create_connect(const entity_addr_t& addr, int type) | AsyncConnectionRef | create一個連接並將其加到conns中 |
submit_message(Message *m, AsyncConnectionRef con,const entity_addr_t& dest_addr, int dest_type) | void | 發送消息的時候會用到,根據目的地址判斷需要發送消息的連接是否存在,以及連接是否是本地連接,如果是本地連接,直接對消息進行dispatch,如果連接不存在,需要根據消息類型創建新的連接 |
_send_message(Message *m, const entity_inst_t& dest) | int | 從連接中查找目的地址,然後調用submit_message()發送消息 |
add_accept(int sd) | AsyncConnectionRef | 新建一個連接,然後將其加入到accepting_conns中 |
2、 Processor類、WorkerPool類和Worker類
Processor類相當於AsyncMessenger模塊中的一個處理器,AsyncMessenger需要完成的很多操作(start、ready、bind等)都是通過Processor來完成的。當Messenger完成地址綁定後,Processor啓動,然後監聽即將到來的連接。就是說AsyncMessenger模塊的一些啓動、綁定、就緒等操作是在Processor相應操作的基礎上封裝的。
Processor類定義了一個AsyncMessenger的對象,一個NetHandler的實例,一個Worker對象。
Processor類的成員變量(方法):
成員變量(方法)名 | 返回值類型 | 描述 |
*msgr | AsyncMessenger | AsyncMessenger的指針實例,用於調用AsyncMessenger中的成員變量(方法),用的最多的還是綁定時獲取的地址信息等。 |
net | NetHandler | 綁定套接字後將其設置爲非阻塞,然後這是套接字選項。 |
*worker | Worker | 工作線程 |
listen_sd | int | 獲取套接口描述字的值,非負表示套接字創建成功,-1表示出錯 |
nonce | uint64_t | 構造函數中用於entity_addr_t的唯一標識ID |
bind(const entity_addr_t &bind_addr, const set& avoid_ports) | int | 執行綁定套接字的具體過程 |
start(Worker *w) | int | 執行消息模塊的start,具體就是啓動線程,讓其處於工作狀態 |
accept() | void | 建立連接的過程,如果連接建立成功,則通過add_accept()函數將連接加入到accepting_conns集合中 |
stop() | void | 關閉套接字 |
rebind(const set& avoid_port) | int | 如果第一次沒有綁定成功或者其它原因導致的綁定失敗,執行重新綁定 |
WorkerPool類是一個線程池,主要作用是創建worker線程,然後將其放入自己的worker容器中,每次創建worker線程的時候根據配置文件的參數ms_async_op_threads來指定worker線程的數量,創建是在WorkerPool的構造函數中進行的。
在WorkerPool類中定義了一個worker集合,用於存放worker線程,還定義了一個coreids,用戶存放cpu id的集合,提供指定cpu運行單個線程的作用。在配置文件中有一個參數是ms_async_affinity_cores,將創建的worker綁定到指定的cpu core上。如果創建了2個線程,綁定的cpu core是0、1,默認的ms_async_affinity_cores值是空的,即使用全部的cpu資源,如果cpu的資源不夠用的時候可以將worker指定cpu core。
WorkerPool類的成員變量(方法)
成員變量(方法)名 | 返回值類型 | 描述 |
coreids | vector | 用於存放CPU id的集合 |
WorkerPool(CephContext *c) | 構造函數 | WorkerPool的構造函數,根據ms_async_op_threads的值創建相應數量的worker線程,同時完成worker和cpu core的綁定。 |
start() | void | 創建worker集合中的worker線程,啓動線程開始工作 |
*get_worker() | Worker | 獲取worker集合中的worker線程 |
get_cpuid(int id) | int | 獲取cpu的id |
workers | Worker* | Worker線程的集合,WorkerPool在構造函數中創建的worker線程放入到這個集合中 |
- Worker類是具體的工作線程,Worker線程的主要工作是一個循環,調用epoll_wait獲取需要處理的事件,用循環來處理這個事件,當外部有操作時,比如讀取消息,註冊一個回調類,創建一個文件事件,然後啓動回調操作即可處理請求。消息模塊啓動時,用一個線程在Worker類中定義了一個WorkerPool對象,一個EventCenter的實例。
Worker類的成員變量(方法)
成員變量(方法)名 | 返回值類型 | 描述 |
*pool | WorkerPool | WorkerPool的實例,在entry()函數中用於獲取cpu的id |
done | bool | 如果線程的工作完成置爲true,否則false |
center | EventCenter | EventCenter的實例,在Worker的構造函數中執行EventCenter的初始化工作 |
*entry() | void | 工作線程的入口函數,啓動一個while循環來執行事件的處理,在整個消息模塊中就使用了這一個工作線程 |
stop() | void | 將done置爲true,然後調用EventCenter的wakeup函數,即停止socket工作 |
3、 AsyncConnection類
AsyncConnection是整個Async消息模塊的核心,連接的創建和刪除、數據的讀寫指令、連接的重建、消息的處理等都是在這個類中進行的。本小節重點分析了其中的重要成員變量和24個成員函數。
AsyncConnection類的成員變量
成員變量名 | 返回值類型 | 描述 |
*async_msgr | AsyncMessenger | AsyncMessenger對象,調用一些環境變量等 |
out_q | map(int, list(pair(bufferlist, Message*)) ) | 存放消息和消息map信息的數據結構 |
sent | list(Message*) | 存放那些需要發送的消息 |
local_messages | list(Message*) | 存放本地傳輸的消息 |
outcoming_bl | bufferlist | 臨時存放消息的bl |
read_handler | EventCallbackRef | 處理讀請求的回調指令 |
write_handler | EventCallbackRef | 處理寫請求的回調指令 |
connect_handler | EventCallbackRef | 處理連接請求的回調指令 |
local_deliver_handler | EventCallbackRef | 處理本地連接請求的回調指令 |
data_buf | bufferlist | 存放數據的bl |
data_blp | bufferlist::iterator | data_buf的指針 |
front, middle, data | bufferlist | 頭部,中間部分和數據部分 |
connect_msg | ceph_msg_connect | 消息連接 |
net | NetHandler | NetHandler的實例,處理網絡連接 |
*center | EventCenter | EventCenter的對象,用來調用事件中心的操作 |
*recv_buf | char | 用於從套接字中接收消息的buf |
AsyncConnection類的成員方法
編號 | 成員方法名 | 返回值類型 | 描述 |
1 | do_sendmsg(struct msghdr &msg, int len, bool more) | int | 返回的是需要被髮送的消息的長度 |
2 | try_send(bufferlist &bl, bool send=true) | int | 加上一個write_lock,然後調用_try_send來真正發送消息 |
3 | _try_send(bufferlist &bl, bool send=true) | int | 如果send的值爲false,會將bl添加到send buffer中,這麼做的目的是避免messenger線程外發生錯誤 |
4 | prepare_send_message(uint64_t features, Message *m, bufferlist &bl) | void | 將m中的數據encode和copy到bl中 |
5 | read_until(uint64_t needed, char *p) | int | 循環讀,調用read_bulk,如果r的值不爲0,一直循環下去 |
6 | _process_connection() | int | 處理連接,根據不同的state狀態執行不同的操作,關鍵點是state的值不同 |
7 | _connect() | void | 首先將STATE_CONNECTING的值賦給state,然後調用dispatch_event_external將read_handler事件添加到external_events集合中 |
8 | _stop() | void | 註銷連接,然後將STATE_CLOSED賦給state,關閉套接字,清理事件 |
9 | handle_connect_reply(ceph_msg_connect &connect, ceph_msg_connect_reply &r) | int | 根據reply.tag值的不同執行不同的操作 |
10 | handle_connect_msg(ceph_msg_connect &m, bufferlist &aubl, bufferlist &bl) | int | 處理消息的連接,如果成功則接收這個連接 |
11 | discard_out_queue() | void | 清除AsyncConnection的消息隊列 |
12 | requeue_sent() | void | 重新將send隊列入隊 |
13 | handle_ack(uint64_t seq) | void | 處理確認信息,刪除send隊列中的message |
14 | write_message(Message *m, bufferlist& bl) | int | 將消息寫到complete_bl中,調用_try_send發送消息 |
15 | _reply_accept(char tag, ceph_msg_connect &connect, ceph_msg_connect_reply &replybufferlist authorizer_reply) | int | 有一個bufferlist結構的reply_bl,調用try_send將reply_bl發送出去 |
16 | is_queued() | bool | 判斷是否入隊列,主要是out_q和outcoming_bl這兩個隊列 |
17 | shutdown_socket() | void | 關閉套接字 |
18 | connect(const entity_addr_t& addr, int type) | void | 在AsyncConnection第一次構造的時候使用,然後調用_connect()函數 |
19 | accept(int sd) | void | 將state的值設置爲STATE_ACCEPTING,然後調用create_file_event函數創建文件事件,調用dispatch_event_external函數將回調指令分發出去 |
20 | send_message(Message *m) | int | 一般需要發送消息的時候都會調用這個函數進行具體的發送操作,在此之前已經完成了連接 |
21 | handle_write() | void | 使用一個while循環調用write_message將data寫入到m中 |
22 | process() | void | 還是根據不同的state值做不同的處理 |
23 | local_deliver() | void | 這個函數主要用來處理本地的消息傳遞 |
24 | cleanup_handler() | void | 清理事件處理助手,將其重置 |
4、 EventCenter類和EventCallback類
- AsyncMessenger消息模塊是基於epoll的事件驅動方式,取代了之間每個連接需要建立一個Pipe,然後創建兩個線程,一個用來處理消息的接收,另一個用來處理消息的發送,其它操作也是藉助線程的方式。不同於SimpleMessenger消息模塊,AsyncMessenger消息模塊使用了事件,所以需要一個處理事件的數據結構,即EventCenter,用一個線程專門用來循環處理,所有的操作都是通過回調函數來進行的,避免了大量線程的使用。在EventCenter定義了一個FileEvent的數據結構和一個TimeEvent的數據結構,大部分事件都是FileEvent。下面介紹EventCenter中的主要成員變量和方法。
成員變量(方法)名 | 返回值類型 | 描述 |
FileEvent | struct | 文件事件類 |
TimeEvent | struct | 時間事件類 |
external_events | deque(EventCallbackRef) | 用於存放外部事件的隊列 |
*file_events | FileEvent | FileEvent的實例 |
*driver | EventDriver | EventDriver的實例 |
time_events | map(utime_t, list(TimeEvent)) | 事件事件的容器 |
net | NetHandler | NetHandler的實例 |
process_time_events() | int | 處理時間事件 |
*_get_file_event(int fd) | FileEvent | 獲取文件事件 |
init(int nevent) | int | 根據不同的宏創建不同的事件處理器;調用create_file_event創建事件。 |
create_file_event(int fd, int mask, EventCallbackRef ctxt) | int | 根據fd和mask創建文件事件,調用add_event函數將創建的事件加入到事件處理器中去處理 |
create_time_event(uint64_t milliseconds, EventCallbackRef ctxt) | uint64_t | 創建time event,然後將其加入到time_events中 |
delete_file_event(int fd, int mask) | void | 刪除file event |
delete_time_event(uint64_t id) | void | 刪除time event |
process_events(int timeout_microseconds) | int | 如果事件是read_cb或者write_cb則調用相應的回調函數來進行處理(由do_request函數來完成);如果不是這兩種事件,則將external_events隊列中的事件取出放入cur_process中,調用一個while一個循環來處理。 |
dispatch_event_external(EventCallbackRef e) | void | 將創建的外部事件放入external_events隊列中 |
- EventCallback是一個接口類,根據操作不同會定義其子類,使用方式用一個虛函數do_request()來回調處理不同的事件,具體的處理是在do_request()中進行的。
5、 EventDriver類、EpollDriver類、KqueueDriver類和SelectDriver類
- 事件中心相當於一個事件處理的容器,它本身並不真正去處理事件,通過回調函數的方式來完成事件的處理。同樣,如何獲取需要處理的事件也不是事件中心來完成的,它只負責處理,具體對需要處理的事件的獲取是通過EventDriver來完成的,EventDriver是一個接口類,其實現主要是由EpollDriver、KqueueDriver和SelectDriver三個類操作的。Ceph支持多種操作系統的使用,如果使用的是Linux操作系統,使用EpollDriver,如果是BSD,使用KqueueDriver,如果都不是的情況下再使用SelectDriver(系統定義爲最壞狀況下)。事件驅動的執行主要依賴於epoll的方式,其中主要有三個函數:epoll_create(在epoll文件系統建立了個file節點,並開闢epoll自己的內核高速cache區,建立紅黑樹,分配好想要的size的內存對象,建立一個list鏈表,用於存儲準備就緒的事件); epoll_ctl(把要監聽的socket放到對應的紅黑樹上,給內核中斷處理程序註冊一個回調函數,通知內核,如果這個句柄的數據到了,就把它放到就緒列表);epoll_wait(觀察就緒列表裏面有沒有數據,並進行提取和清空就緒列表,非常高效)。由於本項目運行在Linux中,所以下面以EpollDriver爲例對Ceph的底層事件驅動進行描述。
EpollDriver的成員變量(方法)
成員變量(方法)名 | 返回值類型 | 描述 |
epfd | int | epoll的文件描述符 |
*events | struct epoll_event | epoll_event的一個對象 |
size | int | 在執行初始化時獲取文件數量 |
init(int nevent) | int | 執行EpollDriver的初始化,主要是調用epoll_create,建立epoll對象 |
add_event(int fd, int cur_mask, int add_mask) | int | 根據事件的mask執行不同的操作,如果是EVENT_READABLE,表示對應的文件描述符可讀,如果是EVENT_WRITABLE,表示文件描述符可寫,然後調用epoll_ctl添加事件 |
del_event(int fd, int cur_mask, int del_mask) | int | 調用epoll_ctl執行事件的修改或者刪除 |
resize_events(int newsize) | int | 清空事件數量 |
event_wait(vector &fired_events, struct timeval *tp) | int | 調用epoll_wait循環處理事件 |
6、NetHandler類
- NetHandler是AsyncMessenger模塊中用於網絡處理的類,其中定義了6個關鍵成員方法,其中的NetHandler::generic_connect()是每個連接都需要使用到的,創建socket、將socket設置爲非阻塞、設置socket選項等都是經常使用的方法,下表對其詳細分析。
成員方法名 | 返回值類型 | 描述 |
create_socket(int domain, bool reuse_addr=false) | int | 創建socket |
generic_connect(const entity_addr_t& addr, bool nonblock) | int | 通信雙方通過該函數產生連接,首先調用create_socket()創建一個socket,然後將創建的socket設置爲非阻塞,完成以後調用系統socket:: connect()建立連接 |
set_nonblock(int sd) | int | 將Socket設置爲非阻塞的 |
set_socket_options(int sd) | void | 調用系統的socket::setsockopt函數,設置套接字的一些關鍵選項 |
connect(const entity_addr_t &addr) | int | 對NetHandler::generic_connect()進行了一個簡單的封裝 |
nonblock_connect(const entity_addr_t &addr) | int | 接口函數,設置非阻塞的連接 |
上文描述了AsyncMessenger基本數據結構及框架,下一章描述代碼流程。