微信跨平臺組件mars-xlog架構分析及遷移思路
最近微信開源了他們的跨平臺組件mars,前段時間看到他們發的 微信終端跨平臺組件 mars 系列(一) - 高性能日志模塊xlog 就已經有點躍躍欲試了,我們打算學習一下并嘗試遷移。
談談xlog
在上面提到的文章中他們提出了幾種方案:
- 內存緩存,緩存到一定閾值寫入文件 優點是效率很高,缺點是在異常情況下 丟日志 ;
- 直接寫入文件(普通IO) 優點是不會丟日志,但是效率低下;
- 直接寫入文件(mmap) 效率較高,不會丟日志,但是編程要求較高;
微信選擇了最后一種方案,關于mmap具體為什么效率高,可以參考 認真分析mmap:是什么 為什么 怎么用 ,這里面說明了mmap之所以快的主要是在讀寫時能夠讓用戶空間與內核空間更容易地交互。
關于XLOG的性能比對、benchmark可以見上面的文章,或 Mars-benchmark 。
我從源碼角度簡單描述一下它是怎么做的,每一條日志的處理流程大約是如下流程:
簡單來說就是三步:
- 將要打的日志附上各種信息(進程、線程、日期)并格式化。
- 將日志寫入高速緩沖區。這塊高速緩沖區是使用mmap映射出來的內存區,被映射的磁盤文件是它新建的一個緩存文件,.mmap2后綴(若mmap失敗,則用內存緩存代替)。每次打log時首先將它寫入高速緩存,這樣當使用mmap時可以保證這條log快速地被寫入磁盤。
- 當高速緩沖區內容寫到一定閾值時(此處為1/3),通知后臺線程將緩沖區的內容寫入文件。
它使用mmap映射一塊固定長度的文件,這樣保證每條log第一時間都被寫入磁盤,由于每次將log寫入目標文件時都會清空高速緩沖區,所以高速緩沖區的內容可以認為沒有被寫入文件,每次啟動時可以檢查緩沖區,若有數據則將它先寫入目標文件,達到 不丟日志 的效果。這塊核心的內容是在 appender.cc\.h 中,大家有興趣可以對照看看。
一點疑問
當我剛看完這個代碼的時候,我心里是有點疑問的,主要在于:
為什么不直接對輸出log的文件進行mmap?而是寫一塊固定區域然后由后臺線程讀這塊緩存區輸出目標文件?
首先,我這種說法是可行的,因為 mmap 是指定目標map文件的偏移量,就可以通過代碼動態擴展map起點,達到始終map 定長區域 的效果,然后直接對這塊map進行輸出。
由于mmap是映射固定長度區域,為保證寫入順利,每次在拓展時我們就拓展固定寬度,并且調用 ftruncate 將其使用’\0’字符填滿,然后每次從第一個非0字符開始寫入。
具體操作如下:
注:
MAP_LENGTH 為map的固定長度區域。每次發現剩余可寫空間小于 MAP_LENGH /2時便拓展 MAP_LENGH /2的長度,并把offset設置為(文件大小- MAP_LENGH /2),這樣每次map區域都能滿足長度為 MAP_LENGTH ,并且動態拓展文件。
但是經過我的實踐,這樣的效果并不好,原因主要是以下兩點:
- 由于目標文件需要動態擴展長度,在map之前需要調用 ftruncate 將其適應到對應長度,這部分會占 一定的開銷 ;
- 動態擴展目標文件,每次也是擴展固定的長度,這部分內容會先被填上 0 ,這樣導致了log文件末尾會有多余的 0 。
讓數據說話吧,在遷移xlog之后,我嘗試了一下直接map目標文件,動態拓展map的策略,比對xlog方案、Java緩存方案,連續打1000條日志(大約620kb內容),平均5次,幾個方案比對下來性能開銷如下:
可以看出,確實直接輸出目標文件的mmap效率 是不好的 。
遷移思路
要將xlog遷移過來有點麻煩,主要是它帶有很多依賴是我不想要的:
- 部分boost,用于filesystem、mmap方面 => 自己手寫封裝
- 部分common代碼,用于線程、互斥鎖 => 使用c++11的 thread庫 代替
- 里面為了適配跨平臺帶了很多宏,我們目前只在Android上用,暫時可以去除。
其實它的核心代碼不多,所以我決定放棄遷移,學習思路就好了。但是它里面有很多可以借鑒、甚至直接拷貝的好輪子,比如其中的buffer系列:
- ptrbuffer.cc/.h 它用于寫入、讀取一塊 固定長度 的內存區;
- autobuffer.cc/.h 它用于寫入、讀取一塊 可變長度 的內存區,它的長度會適應刷入的數據,并且 沒有尾部的額外空數據 ;
這兩個buffer都是可通用的,它們將內存地址偏移量的各種用法封裝地很棒。
在xlog中主要操作的是 log_buffer ,它的作用主要是封裝統一接口。因為當mmap失敗時,需要使用內存緩存來折中處理,它與mmap的操作相同,都是基于某塊內存進行操作。這個 log_buffer 就將對內存的操作交由ptrbuffer,并可以將內容flush到一塊autobuffer上。
在需要將內容寫到目標文件時,它會通知后臺線程將內容刷到autobuffer上,去除尾部的空數據,然后將其寫至文件。
在 log_buffer 中寫入數據時封裝了加密的操作,具體加密實現由 log_crypt.cc\.h 完成,也是可以直接遷移的工具類。
這個思路比較清晰,所以遷移時只要將上面兩個可以移植的buffer遷移過來,如果需要加密再移植一下 log_crypt.cc ,基本上很快就就可以寫出一套像模像樣的xlog出來。
來自:http://blog.desmondyao.com/mars-xlog/