李艷1,2,張玲3,胡術(shù)1,2,李璞1,2,潘倩1,2
?。?. 四川大學(xué) 計算機(jī)學(xué)院,四川 成都 610064;2. 四川大學(xué) 國家空管自動化系統(tǒng)技術(shù)重點實驗室,四川 成都610064;3. 四川大學(xué) 計算機(jī)基礎(chǔ)教學(xué)實驗中心,四川 成都 610064)
摘要:Tbnet采用生產(chǎn)者消費者隊列模型,具有附帶回應(yīng)的報文發(fā)送機(jī)制,對外提供類庫型的接口,應(yīng)用具有多樣性。研究了淘寶開源網(wǎng)絡(luò)庫Tbnet的核心設(shè)計實現(xiàn)、多樣化使用,內(nèi)容包括Tbnet主要類及其類間關(guān)系,客戶端與服務(wù)端間的連接通信過程,以O(shè)ceanBase早期版本為代表的淘寶分布式產(chǎn)品對Tbnet使用的分析,以及該庫向Windows平臺的移植工作。
關(guān)鍵詞:輸入/輸出線程;工作線程;生產(chǎn)者-消費者
中圖分類號:TP393.1文獻(xiàn)標(biāo)識碼:ADOI: 10.19358/j.issn.1674-7720.2017.01.020
引用格式:李艷,張玲,胡術(shù),等. 網(wǎng)絡(luò)庫Tbnet及其應(yīng)用分析[J].微型機(jī)與應(yīng)用,2017,36(1):66-68,72.
0引言
Tbnet廣泛應(yīng)用于TFS、Tair等開源分布式系統(tǒng),不同于僅提供數(shù)據(jù)傳輸?shù)膫鹘y(tǒng)網(wǎng)絡(luò)庫,該庫為客戶端/服務(wù)端提供交互式通信,即客戶端發(fā)出請求,服務(wù)端接收、處理并予以回應(yīng),適用于分布式系統(tǒng)開發(fā)。Tbnet采用對象語義進(jìn)行類的設(shè)計并大量使用類的繼承,實際使用時需繼承IPacketHandler、IServerAdaptor等接口類,重寫類中虛函數(shù)。Tbnet內(nèi)部由一個處理事件的輸入/輸出線程、一個超時檢查線程及工作線程池組成,I/O線程與工作線程間采用單生產(chǎn)者多消費者模式[1]共享加鎖報文列表。本文分析了Tbnet主要類及其消息通信,對比分析了分布式產(chǎn)品使用Tbnet的差異性。
1內(nèi)部實現(xiàn)解析
Tbnet對用戶提供庫類型接口,其使用方式呈多樣化。通過不同方式調(diào)用Transport類對象,可運行為客戶端或服務(wù)端。為與多個服務(wù)端連接通信,客戶端主線程調(diào)用ConnectionManager類指針與由本機(jī)IP地址及其端口號轉(zhuǎn)換的無符號64位整型ipport提供的serverId標(biāo)識的服務(wù)端建立連接。客戶端與服務(wù)端分別繼承IPacketHandler、采用對象適配器模式[2]的IServerAdaptor類,分別重寫handlePacket()實現(xiàn)報文處理流程。因繼承的類接口不同,服務(wù)端在I/O線程或工作線程處理報文,客戶端則在I/O線程處理報文。
1.1主要類
1.1.1線程相關(guān)類
Tbnet采用I/O線程+工作線程池的模型[3],其線程的實現(xiàn)主要通過Runnable、DefaultRunnable以及Channel類完成,其中DefaultRunnable繼承自Runnable,內(nèi)部組合CThread類。Transport類繼承自Runnable,創(chuàng)建一個I/O線程和一個超時檢查線程,線程內(nèi)部包含至多一個eventLoop處理事件;而繼承自DefaultRunnable的PacketQueueThread類則創(chuàng)建工作線程池。
1.1.2IOComponent相關(guān)類
Socket類封裝套接字及其操作函數(shù),SocketEvent類封裝epoll機(jī)制[4],而IOComponent類包含Socket類指針,設(shè)置SocketEvent類指針、引用計數(shù)及回調(diào)函數(shù)等。TCPAcceptor類繼承自IOComponent,完成類似Acceptor接收器功能,服務(wù)端根據(jù)TCPAcceptor類指針觸發(fā)讀事件執(zhí)行accept()生成已連接套接字的Socket來創(chuàng)建TCPComponent類指針。TCPComponent繼承自IOComponent類,TCPComponent根據(jù)init函數(shù)傳入的參數(shù)來判斷選擇客戶端或服務(wù)端,作為客戶端發(fā)起非阻塞連接,服務(wù)端則代表連接成功。
1.1.3Channel相關(guān)類
為跟蹤各報文交互式通信,Tbnet使用Channel進(jìn)行抽象,通過對客戶端Packet設(shè)置信道ID,服務(wù)端回應(yīng)Packet也使用該ID,可確保請求與回應(yīng)一一對應(yīng)。客戶端處理回應(yīng)報文的IPacketHandler類指針定義在Channel,實現(xiàn)了客戶端發(fā)送和接受處理報文的異步化,Channel機(jī)制也為用戶帶來更多可能性。因定義在主機(jī)間正在使用的Channel數(shù)量有限,為避免不斷申請Channel造成內(nèi)存碎片,客戶端發(fā)送請求報文需從ChannelPool中獲取一個空閑Channel,客戶端收到回應(yīng)報文后將該Channel取出并放回ChannelPool。
1.1.4Transport相關(guān)類
Transport類封裝監(jiān)聽與連接,創(chuàng)建Socket、IOComponent類指針,設(shè)置Socket超時檢查。主線程回調(diào)Socket設(shè)置IP地址、端口等信息;回調(diào)IOComponent創(chuàng)建套接字、設(shè)置套接字選項以及監(jiān)聽或連接。因TCPComponent內(nèi)部創(chuàng)建了用于報文收發(fā)的TCPConnection類指針,Transport類創(chuàng)建的I/O線程在EPollSocketEvent類對象的驅(qū)動下,回調(diào)IOComponent進(jìn)行事件處理。超時線程遍歷檢查存儲在vector中的內(nèi)部正在使用的超時IOComponent類指針,再檢查內(nèi)部發(fā)生錯誤已移入刪除列表的超時或引用計數(shù)≤ -10的IOComponent類指針。
1.1.5ConnectionManager相關(guān)類
ConnectionManager類包含Transport類指針,客戶端首次與指定服務(wù)端建立TCP連接時采用長連接復(fù)用的ConnectionManager類指針,調(diào)用Transport類指針建立非阻塞連接,將key為對應(yīng)服務(wù)端序號(serverId)、value為主機(jī)間連接的Connection類指針插入map;待雙方再次連接,客戶端主線程不再調(diào)用Transport建立TCP連接,而是在map中根據(jù)serverId查找Connection類指針,通過該Connection將Packet壓入發(fā)送隊列,等待I/O線程觸發(fā)寫事件。
1.2對象生命周期管理設(shè)計
Tbnet中類繼承、類間相互操作,導(dǎo)致較多類對象的創(chuàng)建與回收不在自身類中。主線程創(chuàng)建Socket類指針,并根據(jù)Socket創(chuàng)建IOComponent類指針。待I/O線程不再調(diào)用Socket及IOComponent時,主線程IOComponent類的析構(gòu)函數(shù)回收Socket,Transport類的析構(gòu)函數(shù)回收在用鏈表及刪除鏈表中的IOComponent指針對象。服務(wù)端主線程創(chuàng)建的TCPAcceptor類指針,客戶端主線程以及服務(wù)端I/O線程分別創(chuàng)建的TCPComponent類指針,主機(jī)I/O線程通過原子計數(shù)修改這些類指針的引用計數(shù),待引用計數(shù)≤-10時,回收該指針。TCPComponent內(nèi)部創(chuàng)建TCPConnection類指針,其析構(gòu)函數(shù)回收該類指針。
1.3消息通信
客戶端與服務(wù)端使用單I/O線程+多線程模型即半同步半異步模式[5],主機(jī)間通信數(shù)據(jù)流程如圖1所示。
客戶端主線程使用工廠類IPacketFactory,根據(jù)數(shù)據(jù)包類型創(chuàng)建Packet類指針,設(shè)置Packet超時時間,將分配的Channel信道ID設(shè)置到Packet頭部,再將Packet依次壓入加鎖的發(fā)送隊列,見步驟1~2;I/O線程將Packet從發(fā)送隊列移至臨時隊列,依次取出Packet并調(diào)用IPacketStreamer類指針將Packet二進(jìn)制轉(zhuǎn)換,Packet組裝后放入輸出緩存,直至臨時隊列為空或輸出緩存可寫空間尚未超過閾值,使用send函數(shù)發(fā)送請求報文直至輸出緩存為空或發(fā)送次數(shù)超過10次,見步驟4。服務(wù)端I/O線程使用recv函數(shù)接收請求報文至空間足夠的輸入緩存,調(diào)用IPacketStreamer類指針解析報文,獲取packet并放入加鎖的報文隊列,見步驟6;工作線程從報文隊列取出并處理Packet,接著回調(diào)Connection將設(shè)置了超時時間、信道ID的回應(yīng)Packet壓入發(fā)送隊列,見步驟7~8;服務(wù)端I/O線程觸發(fā)寫事件,從發(fā)送隊列中取出Packet,組裝并發(fā)送該回應(yīng)報文,見步驟9;客戶端I/O線程觸發(fā)讀事件,接收并處理報文,見步驟10。
2應(yīng)用分析
相較于傳統(tǒng)的提供收發(fā)功能的網(wǎng)絡(luò)庫,面向?qū)ο蟮腡bnet不僅在使用上存在較大差異且不能直接使用,開發(fā)人員需繼承多個接口類,重寫成員函數(shù),還需將其他類如Transport實例化并通過編碼有機(jī)結(jié)合,方可實現(xiàn)通信功能。基于Tbnet的實現(xiàn)呈多樣化,本節(jié)將介紹兩個典型的應(yīng)用:Tbnet自帶示例代碼和OceanBaseV0.3中的應(yīng)用。
2.1示例代碼
Tbnet提供一個簡單的報文通信反射示例[6],包含客戶端EChoClient與服務(wù)端EChoServer兩個進(jìn)程。客戶端/服務(wù)端主線程使用Transport對象建立TCP連接、開啟I/O線程及超時檢查線程、等待線程結(jié)束[7]。示例服務(wù)端無工作線程池,主機(jī)間消息通信流程如圖2所示。
客戶端主線程分配內(nèi)存,創(chuàng)建字符串類型的Packet類指針,將附帶信道ID的請求Packet壓入加鎖的發(fā)送隊列,見步驟1~2;I/O線程從發(fā)送隊列中移出并組裝Packet,發(fā)送請求報文,見步驟4;服務(wù)端I/O線程接收并解析請求報文獲取請求Packet,將請求Packet內(nèi)容直接復(fù)制拷貝到回應(yīng)Packet并設(shè)置對應(yīng)信道ID,再將其壓入發(fā)送隊列,組裝Packet并發(fā)送回應(yīng)報文,見步驟6~7;客戶端I/O線程接收回應(yīng)報文,確認(rèn)收發(fā)數(shù)據(jù)包數(shù)目是否相同,接收完畢關(guān)閉I/O線程,銷毀I/O組件,刪除回應(yīng)數(shù)據(jù)包,見步驟8。
2.2OceanBase中的使用
OceanBase[8]作為分布式關(guān)系數(shù)據(jù)庫,其0.4之前版本均使用Tbnet,除常規(guī)使用,通過添加WaitObject等待對象機(jī)制,客戶端可在主線程阻塞等待回應(yīng)報文。OceanBase采用I/O線程+工作線程池模型[9],主機(jī)間網(wǎng)絡(luò)通信數(shù)據(jù)流程如圖3所示。
客戶端主線程創(chuàng)建含有回應(yīng)報文序號(seq_id)的WaitObject類指針,見步驟1;數(shù)據(jù)包設(shè)置附有seq_id的Channel信道ID并將其壓入加鎖發(fā)送隊列,WaitObject在主線程阻塞等待回應(yīng)報文,見步驟2~3;客戶端I/O線程向服務(wù)端發(fā)送從發(fā)送隊列移出并組裝的請求報文,見步驟5;服務(wù)端I/O線程接收并解析報文獲取請求Packet,將其放入加鎖的報文列表,見步驟6;服務(wù)端工作線程獲取報文并處理,處理完成后工作線程將帶有seq_id的回應(yīng)Packet壓入發(fā)送隊列,見步驟7~8;服務(wù)端I/O線程組裝并發(fā)送回應(yīng)報文,見步驟9;客戶端I/O線程接收回應(yīng)報文并處理,若報文帶有seq_id,則從map中查找對應(yīng)WaitObject并將回應(yīng)報文放入其中,利用條件變量喚醒阻塞等待的主線程;客戶端主線程從WaitObject中獲取回應(yīng)報文,見步驟10~11。
3向Windows平臺的移植實現(xiàn)
Tbnet基于Linux平臺實現(xiàn),為便于使用,需向Windows平臺移植。移植主要思路:(1)Tbnet依賴淘寶另一開源實現(xiàn)Tbsys,即對系統(tǒng)級函數(shù)的封裝,跨平臺移植需先替換Tbsys中的平臺相關(guān)部分;(2)替換Tbnet中I/O復(fù)用模型調(diào)用的epoll機(jī)制。
3.1Tbsys的移植
開源分布式協(xié)調(diào)系統(tǒng)Zookeeper的C語言客戶端的“winport.cpp”文件[10],其包含基于Windows函數(shù)實現(xiàn)的常見POSIX語義的線程函數(shù),包括線程創(chuàng)建、回收、互斥鎖、自旋鎖、讀寫鎖、條件變量等[11],也加載了socket動態(tài)鏈接庫。該實現(xiàn)相對較輕量,滿足Tbnet移植要求,極大地減少了移植工作量。應(yīng)用于IOComponent類指針的引用計數(shù)通過Tbsys原子計數(shù)實現(xiàn),Tbsys使用AT&T嵌入式匯編實現(xiàn)32位整數(shù)的原子操作。在Windows平臺,需使用Interlocked系列函數(shù)來替換Tbsys中atomic類的相關(guān)函數(shù),以此實現(xiàn)自動增加、減少及比較替換功能。
3.2事件處理機(jī)制
Tbnet中EPollSocketEvent類采用epoll機(jī)制實現(xiàn)I/O復(fù)用模型,移植采用select機(jī)制,通過繼承SocketEvent實現(xiàn)。epoll機(jī)制返回事件觸發(fā)的套接字,epoll_ctl()關(guān)注事件,epoll_wait()獲取激活事件;select機(jī)制獲取被觸發(fā)的套接字個數(shù)后,需遍歷整個套接字?jǐn)?shù)組查找事件觸發(fā)的套接字。移植中,為操作方便,封裝的SelectSocketEvent類內(nèi)部包含key為Socket類指針、value為Socket關(guān)聯(lián)的IOComponent類指針的map,考慮到跨線程操作map,使用互斥鎖機(jī)制。select處理流程調(diào)整為:(1)遍歷map將套接字描述符關(guān)注事件加入讀描述符集、寫描述符集;(2)有限時間內(nèi)執(zhí)行select函數(shù),再次遍歷map,將map中迭代器指向的已觸發(fā)事件的value設(shè)置到根據(jù)標(biāo)識判斷觸發(fā)了讀或?qū)懯录腎OEvent類指針的成員變量IOComponent類指針中,返回觸發(fā)事件數(shù)目。
4結(jié)論
作為支持交互式通信的網(wǎng)絡(luò)庫,Tbnet使用時需注意:一個Transport僅有一個I/O線程,對于連接多、吞吐量大的應(yīng)用存在瓶頸[12];因服務(wù)端使用工作線程池處理請求報文,導(dǎo)致報文的處理順序、回應(yīng)報文的發(fā)送順序及其接收順序不一致,對同一客戶端發(fā)出的報文也存在相同情況,該亂序會導(dǎo)致應(yīng)用層面問題,如先進(jìn)先出;服務(wù)端對請求報文的處理既可在I/O線程也可在工作線程中進(jìn)行,客戶端則在I/O線程中處理回應(yīng)報文,這種將應(yīng)用層的處理放入線程的做法,通常存在線程不安全問題,需附加隊列等處理機(jī)制。
參考文獻(xiàn)
?。?] BEVERIDGE J. Win32 多線程程序設(shè)計[M]. 侯捷,譯.武漢:華中科技大學(xué)出版社,2002.
?。?] (美)沙洛韋,(美)特羅特.設(shè)計模式精解[M]. 熊節(jié),譯.北京:清華大學(xué)出版社,2004.
?。?] 陳碩. Linux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫[M]. 北京:電子工業(yè)出版社,2013.
?。?] STEVENS W R, FENNER B,RUDOFF A M. Unix網(wǎng)絡(luò)編程卷1:套接字聯(lián)網(wǎng)API(第3版)[M]. 北京:人民郵電出版社,2015.
?。?] SCHMIDT D C,STAL M, ROHNERT H, et al.面向模式的軟件體系結(jié)構(gòu) 卷2:用于并發(fā)和網(wǎng)絡(luò)化對象的模式[M]. 張志祥,任雄偉,肖斌,等,譯.北京:機(jī)械工業(yè)出版社,2003.
[6] Mao Qi.Tbnet開源[EB/OL]. (2013-11-xx)[20151225].http://code.taobao.org/svn/tbcommonutils/.
?。?] STEVENS W R. UNIX網(wǎng)絡(luò)編程卷2:進(jìn)程間通信(第2版)[M]. 楊繼張,譯.北京:人民郵電出版社,2010.
?。?] OceanBase開源[EB/OL]. (2014-04-xx)[20160402].http://code.taobao.org/svn/OceanBase/.
?。?]黃貴,莊明強(qiáng). OceanBase分布式存儲引擎[J]. 華東師范大學(xué)學(xué)報(自然科學(xué)版),2014(5):164173.
?。?0] The Apache Software Foundation. Zookeeper開源[EB/OL].(2016-01-xx)[20160502].http://wwwus.apache.org/dist/zookeeper/.
?。?1] BUTENHOF D R. POSIX多線程程序設(shè)計 [M]. 丁磊,曾剛,譯.北京:中國電力出版社,2003.
?。?2] 楊傳輝. 大規(guī)模分布式存儲系統(tǒng):原理解析和架構(gòu)實戰(zhàn)[M]. 北京:機(jī)械工業(yè)出版社,2013.