有過嵌入式開發經驗的都知道日誌的存儲是個比較麻煩的問題。以前ARM開源了一款項目:cmbacktrace,github地址就不貼了,能夠將crash時的堆棧信息進行保存。可是對於手機app來講,光保存程序崩潰時的日誌信息時遠遠不夠的,根本沒法定位到具體緣由。在講解淘系使用的日誌系統以前,先看一下最通用的日誌系統。
git
通用方案一github
主流的日誌模塊好比logback,是屬於實時日誌記錄系統,也就是每產生一句日誌就進行加密存入磁盤文件。這樣有個缺點就是I/O過於密集佔用大量CPU資源,影響程序性能,極易卡頓。通常使用與app開發調試階段,正式發版確定要把log功能關閉,等用戶產生程序崩潰等反應問題時,才從新打一個調試包進行復現。可是不少時候場景不徹底相同,很難復現問題。算法
前文有講過cache數據與磁盤數據之間的關係:進程是如何使用內存的?緩存
程序寫文件操做的時候,並非直接操做磁盤裏的文件,而是先把數據寫入系統緩存,也就是髒頁中,而後操做系統的守護進程update進程會定時(通常爲30s)將髒頁更新到磁盤裏。數據寫入磁盤存在兩次拷貝:從用戶空間內存拷貝到內核空間內存,而後從內核空間flush寫入到磁盤。加上具體寫入磁盤的操做沒法由程序控制,所以這裏會形成CPU峯值太高致使性能下降。微信
通用方案二網絡
直接使用Android的logsdk固然方便,可是會形成性能問題,那麼咱們能夠先將日誌保存到內存緩存,達到必定大小後再加密寫入文件。同時爲了減小數據流,先對數據進行壓縮再寫入(借鑑HTTP網絡傳輸的作法)。總體方案以下圖:app
這種方案不須要實時保存日誌,不會產生CPU峯值。可是丟日誌的問題仍然沒解決。好比程序crash異常退出時,不少場景並不會有系統事件通知,也就沒法保存crash時的堆棧。鵝廠的嵌入式操做系統Tencenttiny OS宣稱在stm32系列單板上解決了這個問題,可是使用起來仍然差強人意。更爲重要的是,Android系統比嵌入式系統複雜的多,不少方案沒法直接套用。
異步
手淘優化方案jsp
手淘app主要從兩個方面來提升日誌使用效率。性能
mmap
經過調用mmap存儲映射I/O,具體方案就是將磁盤文件映射到用戶空間緩衝區上,對內存緩衝區執行數據操做時,至關於操做磁盤中的文件。這樣的好處就是不須要使用read和write。映射方案以下圖所示。
使用mmap的好處是免去一次實時數據拷貝,同時能夠經過調用msync、munmap將數據髒頁寫回磁盤文件,對於應用層可控(起一個後臺線程異步執行就行)。優化後具體效果能夠參看APUE圖14-28。
這裏要注意,intmadvise(caddr_t addr, size_t len, int advice);這個接口在Unix說明是能夠經過使用madv_willneed參數來預加載磁盤文件到內存,可是在mac上實驗並沒什麼效果,所以妥善的方案是對每一個頁執行讀取一個字節的操做,這樣保證文件加載到內存中。
數據壓縮
數據是先加密仍是先壓縮?標準作法是先壓縮再加密。明文通常都有冗餘度,壓縮以後內容會變少。相反若是先加密,會破壞文件的冗餘度。通用的壓縮方法主要有gzip、huffman等,手淘借鑑HTTP2的hpack頭部壓縮,在facebook開源的zstd壓縮算法基礎上進行了網絡傳輸的優化。由於日誌中一般會有大量相同特性的短語,所以會在服務端常駐一個字典,而且適時更新由客戶端拉取,經過字典壓縮提高壓縮率。同時爲了防止數據損壞影響整個壓縮包沒法解壓,採用流式壓縮(即日誌達到32k時進行壓縮)。分塊壓縮雖然整體效率略低,可是由於不會在短期內幾種打包壓縮,不會形成CPU峯值。
實際使用過程當中還對外置SD卡進行了適配,當SD卡被刪除時會將日誌存入臨時緩存,每次進程被殺掉時清理緩存,防止長時間佔用用戶空間形成輿情。
參考:
https://github.com/facebook/zstd
APUE 第14章
本文分享自微信公衆號 - 機械猿(on_ourway)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。