分布式日志收集系統(tǒng): Facebook Scribe之結(jié)構(gòu)及源碼分析
我的獨立博客網(wǎng)址是:http://wuyouqiang.sinaapp.com/。
我的新浪微博:http://weibo.com/freshairbrucewoo。
歡迎大家相互交流,共同提高技術(shù)。
scribe結(jié)構(gòu)及源碼詳細(xì)分析
1. 整體類關(guān)系圖

2. 客戶端寫日志序列圖

3. 活動及狀態(tài)圖

Scribe活動圖

4. 啟動代碼詳解

啟動過程流程圖
(1) 調(diào)用setrlimit函數(shù)設(shè)置能夠打開的最大文件數(shù)為65535;
(2) 調(diào)用getopt_long函數(shù)解析運行scribe所帶參數(shù)信息,如-p port指定運行端口號;
(3) 調(diào)用srand、time和getpid產(chǎn)生唯一的隨機(jī)種子(不知道有什么作用);
(4) 根據(jù)端口號和配置文件new一個scribe服務(wù)器全局控制器對象g_Handler:scribeHandler類型;
(5) 調(diào)用initialize()函數(shù)初始化scribe服務(wù)器---->設(shè)置scribe運行狀態(tài)信息---->調(diào)用StoreConf的parseConfig解析配置文件信息(解析過程后面單獨詳解)---->根據(jù)解析的配置文件信息載入全局配置信息到程序---->根據(jù)解析的配置文件信息調(diào)用configureStore函數(shù)配置模型信息---->獲取存儲分類名稱并保存---->根據(jù)分類名稱調(diào)用configureStoreCategory建立存儲隊列(包含有存儲類型,具體的消息存入是由相應(yīng)store完成的);
(6) 調(diào)用scribe::startServer()啟動scribe服務(wù)器。
說明:上面第五點的箭頭代表調(diào)用其他函數(shù)實現(xiàn)功能,就是函數(shù)一直嵌套下去。啟動代碼的文件是scribe_server.cpp,入口函數(shù)是main。
5. 配置文件解析源碼詳解
(1) 配置文件解析入口是在啟動代碼被調(diào)用的parseConfig函數(shù),唯一的參數(shù)就是配置文件的名稱;
(2) 調(diào)用readConfFile函數(shù)讀入配置文件到一個字符串隊列中,每一行數(shù)據(jù)為隊列中的一個值,通過ifstream打開文件流,并getline一行一行的讀入數(shù)據(jù),并壓入隊列;
(3) 調(diào)用parseStore函數(shù)來解析存儲的配置信息,參數(shù)是剛才讀入的字符串隊列和this指針(這個參數(shù)的作用是把解析的信息存入這個對象中,這個參數(shù)本身意義不大,但是在內(nèi)部遞歸調(diào)用的時候需要新建一個StoreConf的對象存放下一級的配置信息時,就必須傳入這個參數(shù),所以統(tǒng)一考慮這個函數(shù)就設(shè)計成兩個參數(shù),第一調(diào)用就把this作為參數(shù)就可以了);
(4) 在parseStore函數(shù)中一行一行的取出,然后去掉注釋和空白。然后判斷這次讀入的行是不是store開始行(<store>)或結(jié)束行(</store>)。如果是開始行就繼續(xù)遞歸parseStore函數(shù)解析下一行數(shù)據(jù);如果是結(jié)束行就解析完畢;如果都不是代表是一個配置項參數(shù)設(shè)置(名稱=值),就分別提取出參數(shù)名稱和值,并按鍵值對存放入map中。
(5) 配置文件解析完畢,解析的結(jié)果就按鍵值對存放在StoreConf的對象中,以后哪一個需要使用參數(shù)時直接在里面查找就可以了。
6. 存儲配置詳解
(1) 在啟動代碼詳解中說明了存儲信息的配置是通過configureStore和configureStoreCategory著兩個函數(shù)實現(xiàn)的;
(2) 在configureStore函數(shù)中根據(jù)傳遞進(jìn)來的StoreConf對象存放的配置信息,解析出此store存放哪個(參數(shù)名稱category:單個分類)或哪幾個(參數(shù)名稱categories:多個分類mutil)分類的消息,并將其保持到分類向量中,然后針對單個和多個分類分別創(chuàng)建StoreQueue對象來執(zhí)行消息的分發(fā)處理;
(3) 單個分類:直接調(diào)用configureStoreCategory創(chuàng)建StoreQueue對象;
(4) 多個分類:先調(diào)用針對分類列表的調(diào)用創(chuàng)建一個StoreQueue對象副本,后然根據(jù)分類的數(shù)量依次拷貝這個副本創(chuàng)建StoreQueue對象;
(5) 每創(chuàng)建一個StoreQueue對象就對這個對象計數(shù)的變量numstores加1操作;
(6) 在configureStoreCategory函數(shù)中首先確實是否是一個前綴分類,然后根據(jù)model是否為null來決定是拷貝一個StoreQueue對象還是新建一個StoreQueue對象。如果是拷貝,判斷是否為每一個分類都創(chuàng)建一個線程并且不是默認(rèn)的分類和前綴分類,如果是就調(diào)用StoreQueue的拷貝構(gòu)造函數(shù)生成一個StoreQueue對象;如果不滿足條件就直接賦值表示已經(jīng)存在分類了。如果是新建就根據(jù)各種條件生成新建需要的各個參數(shù)值調(diào)用StoreQueue構(gòu)造函數(shù)生成新對象。接著如果是拷貝的就直接打開StoreQueue(調(diào)用StoreQueue的open),否則需要配置在打開(調(diào)用StoreQueue的configureAndOpen)。最后將相應(yīng)的分類或前綴分類存放入對象的map中,把新建StoreQueue對象也存放入StoreQueue向量中。
7. StoreQueue功能詳解
(1) 在4中的(6)中介紹了在configureStoreCategory函數(shù)中分別用了構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)創(chuàng)建StoreQueue對象;
(2) 在StoreQueue構(gòu)造函數(shù)中用初始化列表初始化了各個配置變量,然后調(diào)用Store的全局createStore函數(shù)創(chuàng)建一個Store對象(后面詳解Store模塊功能),最后調(diào)用storeInitCommon函數(shù)初始化用于多線程的互斥和條件變量并創(chuàng)建啟動線程(model為true不創(chuàng)建);拷貝構(gòu)造實現(xiàn)同樣功能,只是很多配置變量的初始化直接拷貝;
(3) 一個全局的線程入口函數(shù)threadStatic,參數(shù)為一個StoreQueue對象,啟動這個線程以后,每個StoreQueue對象調(diào)用自己的線程成員函數(shù);
(4) 線程成員函數(shù)threadMember開始執(zhí)行,初始化最后一次檢查存儲的時間為0和最后一次處理消息為當(dāng)前時間,然后開始處理命令(StoreCommand描述,這里處理三種CMD_OPEN、CMD_CLOSE和CMD_CONFIGURE),如果是CMD_CONFIGURE命令就會啟動在線配置(調(diào)用函數(shù)configureInline實現(xiàn)),在線配置會針對具體的存儲類型配置相應(yīng)的存儲類型(例如是file存儲就會配置file存儲相應(yīng)的參數(shù)),調(diào)用Store的confige實現(xiàn)(動態(tài)綁定到具體的實現(xiàn)類)。接著根據(jù)設(shè)置的檢查存儲的時間間隔看是否超過,超過就開始執(zhí)行存儲檢查(實現(xiàn)函數(shù)是Store的periodicCheck,同樣利用多態(tài)動態(tài)綁定)。下面繼續(xù)執(zhí)行處理消息的任務(wù),兩種情況下都需要處理消息:一是超過了設(shè)置的最大寫入時間間隔;二是消息長度超過了設(shè)置的目標(biāo)長度(緩存功能),如果有失敗的消息沒有處理就先處理失敗的消息。處理消息是調(diào)用Store的handleMessages函數(shù),如果處理失敗調(diào)用StoreQueue的processFailedMessages函數(shù)將處理失敗的消息保存起來,以便下次繼續(xù)處理,防止消息(或數(shù)據(jù))丟失。最后沒有需要處理的消息或命令時讓本線程掛起等待,并根據(jù)設(shè)置的存儲檢查時間為等待設(shè)置超時,以便能夠定期檢查存儲。
(5) 線程函數(shù)在沒有收到CMD_STOP命令會一直執(zhí)行下去。
8. Store以及各個繼承子類代碼詳解
(1) store類:函數(shù)createStore根據(jù)存儲類型創(chuàng)建相應(yīng)的子類對象,其他的實現(xiàn)的方法都很簡單,一句話的事,一看就明白,具體處理消息的方法在相應(yīng)的子類中實現(xiàn)。
(2) FileStoreBase類:
a) 這個是文件存儲共同的基類,不同的文件格式寫入具體的子類實現(xiàn);
b) 它的構(gòu)造函數(shù)用函數(shù)初始化列表初始化了所有的文件存儲的配置參數(shù),config函數(shù)對默認(rèn)的參數(shù)進(jìn)行重新配置,copyCommon函數(shù)復(fù)制已有對象的配置信息參數(shù);
c) Open函數(shù)調(diào)用子類具體openInternal函數(shù),具體實現(xiàn)子類中介紹;
d) periodicCheck函數(shù)檢查是否符合滾動文件,如果滿足調(diào)用滾動文件函數(shù)rotateFile;
e) rotateFile函數(shù)調(diào)用printStatus函數(shù)根據(jù)配置是否記錄滾動狀態(tài)的信息來決定是否創(chuàng)建并寫入狀態(tài)信息到狀態(tài)文件,然后調(diào)用子類openInternal函數(shù)滾動文件創(chuàng)建;
f) 其他一些基本函數(shù)實現(xiàn)功能:根據(jù)時間配置信息制作完全文件名,制作基本文件名,找最新和最舊文件,制作符號鏈接的完全文件名和基本文件名,找到文件后綴,對齊到塊大小,設(shè)置主機(jī)子目錄。
(3) FileStore類:
a) 此類繼承FileStoreBase類,構(gòu)造函數(shù)調(diào)用基類構(gòu)造函數(shù)初始化基本配置信息,然后初始化列表初始化此類單獨用的配置參數(shù)信息,config函數(shù)重新配置默認(rèn)的參數(shù)信息
b) openInternal函數(shù)根據(jù)滾動類型(rollPeriod)配置和當(dāng)前的時間新建存儲的文件名,并根據(jù)需要創(chuàng)建相應(yīng)目錄、符號鏈接文件和緩存文件;根據(jù)創(chuàng)建過程返回信息設(shè)置狀態(tài)信息等;文件和目錄的創(chuàng)建都是通過FileInterface類提供的接口完成了,具體創(chuàng)建哪種類型的文件(目前只支持STD和hdfs)由子類實現(xiàn);
c) 處理消息函數(shù)handleMessages是重點功能,首先它確保文件打開,然后調(diào)用writeMessages函數(shù)將消息寫入文件;
d) writeMessages函數(shù)執(zhí)行具體的寫入過程,根據(jù)配置組合需要寫入消息的字符串通過FileInterface類的write方法寫入文件;
e) 其他函數(shù)功能:刪除、替換和讀最老文件,判斷一個時間點的文件是否為空等。
(4) ThriftFileStore類:和FileStore類的功能基本相同。
(5) BufferStore類:
a) 構(gòu)造函數(shù)和config參數(shù)配置函數(shù)和其他存儲類都是同樣的功能,只是初始化和配置的參數(shù)都是各自存儲需要的,本類的配置涉及到主從存儲的配置,配置好以后就調(diào)用createStore創(chuàng)建對于的存儲類型,然后根據(jù)主從配置采用的存儲類型在調(diào)用相應(yīng)的config配置函數(shù);copy函數(shù)復(fù)制本類以創(chuàng)建好的一個對象及它的配置信息;
b) changeState函數(shù)改變buffer存儲的當(dāng)前狀態(tài)(三種:STREAMING、DISCONNECTED和SENDING_BUFFER),每種狀態(tài)下處理消息是不同的,所以這個狀態(tài)也很重要;
c) handleMessages函數(shù)就是處理消息,根據(jù)不同的狀態(tài)信息做不同的消息處理,分別調(diào)用主存儲和從從存儲的消息處理函數(shù);這里面有很重要的一點內(nèi)容是:如果我們設(shè)置了自適應(yīng)算法確定的重試時間的參數(shù),就會調(diào)用函數(shù)setNewRetryInterval來設(shè)置具體的重試時間。這個消息處理函數(shù)首先用主存儲來處理消息,如果處理失敗改變狀態(tài),后面狀態(tài)改變了就會執(zhí)行從存儲來處理消息。
d) setNewRetryInterval函數(shù)設(shè)置重試時間;
e) periodicCheck函數(shù):定期檢查存儲函數(shù);首先檢查主從存儲的存儲,不同的存儲類型有不同的檢查功能;如果現(xiàn)在處于DISCONNECTED狀態(tài)并且現(xiàn)在的時間減去最后一次嘗試打開的時間大于重試時間,就嘗試重新打開主存儲(因為當(dāng)主存儲不可用的情況下才會進(jìn)入DISCONNECTED狀態(tài)),根據(jù)打開結(jié)果重新設(shè)置現(xiàn)在的狀態(tài);如果是SENDING_BUFFER狀態(tài)并且是刷新流,就判斷存儲隊列的大小是否大于設(shè)置的最大存儲隊列大小乘以設(shè)置的某個百分比,如果大于直接返回了保持現(xiàn)在的狀態(tài),以便有時間讓消息可以直接發(fā)生到主存儲處理,不用在到本地緩存,提高了一定的效率;后面接著讀取本地緩存中的文件數(shù)據(jù)并交給主存儲處理,如果處理成功就刪除本地緩存,否則將這些沒有成功處理的消息重新放回文件,以便以后處理,如果放回本地緩存出錯,這些消息就丟失,報告一個數(shù)據(jù)丟失的信息;
f) 其他功能函數(shù):打開、關(guān)閉和判斷是否打開等。
(6) NetworkStore類:
a) 配置、構(gòu)造函數(shù)、copy、open、isOpen、close等和其他存儲分類功能相似;
b) periodicCheck函數(shù)唯一功能就是定期檢查服務(wù)器的IP和端口是否改變,如果改變先關(guān)閉鏈接,然后重新設(shè)置IP和端口,最后在重新打開鏈接;
c) handleMessages函數(shù),如果消息的長度大于設(shè)置的瓶頸值就先發(fā)送一個空的消息測試;發(fā)送根據(jù)配置選擇是否使用連接池。
(7) BucketStore類:
a) 配置、構(gòu)造函數(shù)、copy、open、isOpen、close等和其他存儲分類功能相似;
b) createBuckets和createBucketsFromBucket函數(shù)根據(jù)配置參數(shù)和規(guī)則創(chuàng)建相應(yīng)的存儲目錄和文件,為每個配置的bucket創(chuàng)建配置的存儲并配置;
c) periodicCheck函數(shù):先就bucket的數(shù)量生成隨機(jī)數(shù)序列,然后根據(jù)這個序列一次調(diào)用每個bucket配置的相應(yīng)存儲類型存儲檢查函數(shù);
d) handleMessages函數(shù):首先調(diào)用bucketize函數(shù)(根據(jù)不同配置有不同的算法確定)確定寫入哪一個bucket,然后判斷是否需要移除消息里面的key,需要就移除后寫入,不需要就直接寫入;如果寫入失敗把消息保存起來。
(8) NullStore類:不將消息記錄下來,只是簡單的留下一個被忽略的記錄。
(9) MultiStore類:
a) 配置、構(gòu)造函數(shù)、copy、open、isOpen、close等和其他存儲分類功能相似;
b) periodicCheck函數(shù):
c) handleMessages函數(shù):分別調(diào)用每一個存儲相應(yīng)的消息處理函數(shù),根據(jù)配置決定是有一個處理成功就是成功還是所有的處理成功才算成功;
(10) CategoryStore類:分別調(diào)用每一個存儲相應(yīng)的存儲檢查函數(shù)。
(11) MultiFileStore類:只有框架,還沒有具體實現(xiàn)什么功能!
(12) ThriftMultiFileStore類:只有框架,還沒有具體實現(xiàn)什么功能!
9. File相關(guān)(FileInterface、StdFile和HdfsFile)
a) 這幾個類主要實現(xiàn)了文件系統(tǒng)的常用操作,比如創(chuàng)建文件、打開和關(guān)閉文件、計算文件長度等;
b) 實現(xiàn)文件系統(tǒng)常用功能主要使用的是boost庫里面處理文件系統(tǒng)的部分庫函數(shù)(boost::filesystem);
c) 這些類是最終實現(xiàn)消息寫入文件的地方,和我們平時直接讀寫文件類似,前面幾個模塊介紹了怎樣一步一步到達(dá)最后這里,前面消息基本上都是在緩存中處理。
10.總結(jié):今天把以前自己分析scribe的源碼的文檔與大家分享了,里面并沒有涉及到具體的源代碼,算不上真正的源代碼分析,主要介紹了一些源碼實現(xiàn)的功能,有了這些功能說明,你去看源代碼可能會更加快捷一些!粘貼一些源碼本來不是什么費勁的事情,但是我覺得看源代碼最好還是完整的看或者至少是一個完整的模塊的去看更好,更能體會源碼設(shè)計者的思路、思想和編碼技巧。如果你想更深入理解學(xué)習(xí)scribe的原理并通過源碼去分析上一篇博文提到的各種配置選項的用作,那么你可以結(jié)合本篇更加詳細(xì)去分析scribe源代碼!源代碼可以到google上搜索!
浙公網(wǎng)安備 33010602011771號