Mudo C++網絡庫第五章學習筆記

高效的多線程日誌

  • 日誌(logging)有兩個意思:
    • 診斷日誌(diagnostic log), 經常使用日誌庫提供日誌功能;
    • 交易日誌(transaction log), 用於記錄狀態變動, 經過回放日誌能夠逐步恢復每一次修改後的狀態;
  • 日誌一般用於故障診斷和追蹤(trace), 也可用於性能分析;
  • 日誌一般是分佈式系統中事故調查時的惟一線索, 用來追尋蛛絲馬跡, 查出原兇;
    • Log Everything All The Time;
  • 關於進程, 日誌一般要記錄:
    • 收到每條內部消息的ID(還能夠包括關鍵字段、長度、hash等);
    • 收到的每條外部消息的全文;
    • 發出每條消息的全文, 每條消息都有全局惟一的id;
    • 關鍵內部狀態的變動, 等等;
  • 每條日誌都有時間戳, 這樣就能完整追蹤分佈式系統中一個事件的前因後果;
  • 一個日誌文件可分爲前端(frontend)和後端(backend)兩部分;
    • 前端提供應用程序使用的接口(API), 並生成日誌消息(log message);
    • 後端則負責把日誌消息寫到目的地(destination);
    • 典型的多生產者-單消費者問題, 對生產者(前端)而言, 要儘可能作到低延遲、低CPU開銷、無阻塞;
    • 對消費者(後端)而言, 要作到足夠大的吞吐量, 並佔用較少資源;
  • 對C++程序而言, 最好整個程序(包括主程序和程序庫)都使用相同的日誌庫, 日誌有一個總體的日誌輸出, 並且不要各個組件有各自的日誌輸出;
    • 從這個意義上講, 日誌庫是個singleton模式(單例模式);
  • muduo沒有用標準庫中的iostream, 而是本身寫的LogStream class, 主要是出於性能緣由;

功能需求

  • 日誌消息有多種級別(level): TRACE、DEBUG、INFO、ERROR、FATAL等;
  • 日誌消息可能有多個目的地(appender), 如文件、socket、SMTP等;
  • 日誌消息的格式可配置(layout), 例如org.apache.log4j.PatternLayout;
  • 能夠設置運行時過濾器(filter), 控制不一樣組件的日誌消息的級別和目的地;
  • 質量保證(QA)測試環境的時候輸出DEBUG級別的日誌, 在生產環境輸出INFO級別的日誌;
  • 只要調用muduo::Logger::setLogLevel()就能當即生效;
  • 對於分佈式系統中的服務進程而言, 日誌的目的第(destination)只有一個:本地文件;
    • 日誌文件的滾動(rolling)是必需的, 這樣能夠簡化日誌歸檔(archive)的實現;
  • rolling的條件一般有兩個: 文件大小(例如每寫滿1GB就換下一個文件)和時間(例如天天零點新建一個日誌文件, 不論前一個文件有沒有寫滿);
    • 通常的日誌庫都會自動根據文件大小和時間來主動滾動日誌文件;
    • 能主動rolling, 天然也就沒必要支持SIGUSR1了, 畢竟多線程程序處理signal很麻煩;
  • 日誌庫的壓縮與歸檔(archive)不是日誌庫應該有的功能, 而應該交給專門的腳本去作;
  • 磁盤空間監控也不是日誌庫的必備功能;
  • muduo日誌庫: 其一是按期(默認3秒), 將緩衝區內的日誌消息flush到磁盤;
    • 其二是每條內存中的日誌消息都帶有cookie(或者叫哨兵值/sentry), 其值爲某個函數的地址, 這樣經過core dump文件中查找cookie就能找到還沒有來得及寫入磁盤的消息;
  • 日誌消息格式有幾個要點:
    • 儘可能每條日誌佔一行;
    • 時間戳精確到微妙;
    • 始終使用GMT時區(Z);
    • 答應線程id;
    • 打印日誌級別;
    • 打印源文件名和行號;

性能需求

  • 日誌庫的高效性體如今幾個方面:
    • 每秒寫上千萬條日誌的時候沒有明顯的性能損失;
    • 能應對一個進程生產大量日誌數據的場景, 例如1GB/min;
    • 不阻塞正常的執行流程;
    • 在多程序程序中, 不形成爭用(contention);
    • 磁盤帶寬約是110MB/S, 日誌庫應該能瞬時寫滿這個帶寬(沒必要持續過久);
    • 假如每條日誌消息的平均長度是110字節, 這就意味着1秒要寫100萬條日誌;
  • muduo日誌庫實現了幾點優化措施:
    • 時間戳字符串中的日期和時間部分是緩存的, 一秒內的多條日誌只須要從新格式化微妙部分;
    • 日誌消息的前4個字段是定長的, 所以能夠避免在運行期求字符串長度(不會反覆調用strlen);
      • 由於編譯器認識memcpy()函數, 對於定長的內存複製, 會在編譯期把它的inline展開爲高效的目標代碼;
    • 線程id是預先格式化爲字符串, 在輸出日誌消息時只須要簡單拷貝幾個字節;
    • 每行日誌消息的源文件名部分採用了編譯期計算來得到basename, 避免運行期strrchr()開銷;

多線程異步日誌

  • 多線程程序對日誌庫提出了新的需求:線程安全, 即多個程序能夠併發寫日誌, 兩個線程的日誌消息不會出現交織;
  • 多線程的每一個進程最好只寫一個日誌文件;
    • 用一個背景線程收集日誌消息, 並寫入日誌文件, 其餘業務線程只管往這個日誌線程發送日誌消息, 這稱爲異步日誌;
    • 非阻塞日誌;
  • 咱們須要一個隊列將日誌前端的數據傳送到後端(日誌線程);
    • muduo日誌庫採用的是雙緩衝(double buffering)技術;
    • 基本思路是準備兩塊buffer:A和B, 前端負責往buffer A填數據(日誌消息), 後端負責將buffer B的數據寫入文件;
    • 當buffer A寫滿以後, 交換A和B, 讓後端將buffer A的數據寫入文件, 而前端則往buffer B填入新的日誌消息, 如此往復;
    • 爲了及時將日誌消息寫入文件, 即使buffer A未滿, 日誌庫也會每3秒執行一次上述交換寫入操做;
  • 對於異步日誌來講, 這是典型的生產速度高於消費速度問題, 會形成數據在內存中的堆積, 嚴重時引起性能問題(可用內存不足)或程序崩潰(分配內存失敗);
    • 直接丟棄掉多餘的日誌buffer, 以騰出內存, 這樣能夠防止程序庫自己引發程序故障, 是一種自我保護措施;
    • 也能夠加入網絡報警功能, 通知人工介入, 以儘快修復故障;
  • 性能不能憑感受說了算, 必定要有典型場景的測試數據作爲支撐;
  • muduo庫的異步日誌實現用了一個全局鎖;
    • java的ConcurrentHashMap那樣用多個桶子(bucket), 前端寫日誌的時候再按線程id哈希到不一樣的bucket中, 以減小contention(後端實現比較複雜);
    • Linux默認會把core dump寫到當前目錄, 並且文件名是固定的core;
相關文章
相關標籤/搜索