新一代雲原生日誌架構 - Loggie的設計與實踐

語言: CN / TW / HK

Loggie萌芽於網易嚴選業務的實際需求,成長於嚴選與數帆的長期共建,持續發展於網易數帆與網易傳媒、中國工商銀行的緊密協作。廣泛的生態,使得項目能夠基於業務需求不斷完善、成熟。目前已經開源:https://github.com/loggie-io/loggie

1. 背景

嚴選日誌平台初期,使用filebeat採集雲內日誌,用flume採集雲外日誌。期間經歷了一段痛苦的運維排障時期,被問的最多的幾個問題:

  • 某條日誌為何沒有采集?
  • 某條日誌為何重複採集了?
  • 能否將漏採集的那條日誌補採集?
  • 某個日誌文件為何沒有采集?
  • 某個日誌文件的採集速度怎麼這麼慢(延遲超過30s)?
  • 服務重新發布後,之前的日誌怎麼沒有了?

而且同時維護了2套日誌採集agent,維護成本高。不管是filebeat還是flume都存在以下幾點嚴重的問題:

  • 採集性能低: 大促時間,部分節點的日誌打印速度超過100MB/s,然而filebeat的採集性能上限只有80MB/s左右,flume更差一點。
  • 資源佔用高: filebeat單節點採集文件超過100個,cpu使用率超800%;flume空跑內存就得佔用200MB+,大促期間不得不開啟限流以避免影響核心業務系統。
  • 擴展性差: fliebeat複雜的架構以及單output設計無法滿足多變的業務需求。

同時也調研其他開源的日誌採集agent,或多或少都存在上述問題,且都沒有足夠的可觀測性和運維手段(工具)來幫助運維排障,更不用説完整的日誌解決方案。

因此我們走向了自研的道路。

loggie在2022年初已開源:https://github.com/loggie-io/loggie

歡迎大家使用,與社區一起成長!

2. 架構設計

基於golang,借鑑經典生產者-消費者模式的微內核設計。一個pipeline只有source、queue、sink、interceptor4個組件概念,且interceptor也不是必須的。pipeline支持配置熱加載,組件熱插拔,pipeline之間強隔離,防止相互影響。

2.1 pipeline設計

pipeline的設計初衷主要是為了隔離性。之前運維遇到的一個嚴重問題:雲內使用filebeat採集多個業務的多個日誌文件,但是其中一個日誌文件的採集配置的topic被誤刪,導致發送到kafka失敗而阻塞,進而導致整個物理機幾點的所有日誌都阻塞。因此我們需要一種泳道隔離手段,來隔離不用業務、不同優先級的日誌,避免相互影響。

2.1.1 易擴展

pipeline的簡潔設計使得我們的擴展極其便捷。在概念上,一個pipeline只有4種組件:source、queue、sink、interceptor,而且interceptor不是必須的。越少的概念,擴展者者就有越少的學習成本。並且,為了進一步提高擴展性,pipeline中的所有組件都抽象為component,所有的組件擁有一致的生命週期與實現方式。不僅方便了擴展,也方便了loggie對組件的統一管理。

2.1.2 隔離性與reload

基於pipeline,我們實現了配置熱更新,組件熱加載。reloader與discovery組件可以基於K8S CRD、http監聽等方式(預留接口,可以對接例如zookeeper、consul、apollo等配置中心),以pipeline維度進行reload。因此,在保證了reload能力的同時仍然滿足了pipeline隔離的目的。

2.1.3 功能增強:interceptor

interceptor在loggie中是一個可選的組件,卻在loggie中扮演着非常重要的角色。loggie中絕大多數的增強功能都是基於interceptor實現的,例如限流、背壓、日誌切分、encode&decode、字符編碼、結構化等。用户可以根據實際情況選擇對應的interceptor提升loggie的適應能力。

完整內置interceptor:https://loggie-io.github.io/docs/reference/pipelines/interceptor/overview/

3. 日誌採集

對於一個日誌採集agent來説,通常需要重點關心以下3點的設計與實現:

  • 高效採集: 高效指的是高性能的同時低能耗,即如何採集的快服務器資源佔用有小。
  • 公平性: 例如寫入快的文件不能影響寫入慢的文件採集、最近更新的文件不能影響之前更新的文件的採集,刪除文件的合理採集與釋放。
  • 可靠性: 日誌不丟失。包括進程崩潰重啟、服務發佈&遷移&容器漂移、下游阻塞等情況。

3.1 高效採集

日誌採集,我們關心的是這麼幾個問題:

  • 如何及時發現新創建的文件?
  • 如何及時發現最新的寫入?
  • 如何快速讀取文件?

這其實是兩方面的問題:

  • 事件感知: 及時發現文件事件。例如文件新建、刪除、寫入。
  • 文件讀取: 高效讀取文件內容。儘可能快的讀取文件內容,減少磁盤io,cpu可控。

3.1.1 事件感知

如何做到文件事件感知呢?業界有兩種做法:

  • 定時輪詢: 定時檢查目錄文件狀態。
  • OS事件通知: 註冊OS文件事件,由OS通知具體的文件事件。

兩種方式各有利弊:

  • 定時輪詢:

    • 優點:實現簡單,安全可靠。
    • 缺點:輪詢的時間間隔不太好確定,間隔太短耗cpu,間隔太長髮現事件就太慢。
  • OS事件通知:

    • 優點:能夠第一時間發現文件事件

    • 缺點:

      • 實現成本高(不同OS的實現方式不同,需要適配)。
      • 存在bug:例如在最常用的linux系統中存在一些bug(https://man7.org/linux/man-pages/man7/inotify.7.html) ,其中影響最大的幾點:通知丟失、註冊數量有上限、文件改名事件無法通知改名後的文件名。
      • 使用有限制:例如無法對通過 NFS(Network File System) 掛載的網盤&雲盤生效,無法對 FUSE(filesystem in userspace) 生效等。

因此loggie結合兩者共同實現了一個安全可靠,卻又能及時感知事件的方案:

同時啟用定時輪詢和OS通知,使用OS通知然後搭配一個相對較長(loggie默認為10s)的定時輪詢,將兩者發現的事件進行合併以減少重複處理,這樣就能同時兼具兩者的優點。

但是實際測試下來,我們發現了cpu佔用上升,分析原因:

  • OS事件過多: 特別是寫文件的時候,對應有兩個os事件(chmod+write),一旦文件寫得頻繁,os的事件將非常多,loggie疲於處理系統事件。

所以,我們重新分析,什麼樣的事件是我們真正關心並需要及時感知的呢?

  • 文件寫事件?

當我們無法及時發現文件寫事件,會有什麼影響呢?有兩種情況:

  • 如果這個文件正處於採集中(持有文件句柄),那這個文件的寫事件沒有影響。因為正在讀這個文件,後續的寫入理所當然能被讀到。
  • 如果這個文件處於不活躍狀態(即文件已經讀取到了末尾,並且一定時間內沒有發現新內容,甚至文件的文件句柄被釋放了),這個情況我們希望能及時感知文件的寫事件以方便我們及時採集最新的寫入內容。

因此,重要的是“不活躍”文件的寫事件。

  • 文件新建(滾動)事件?

當我們沒有及時發現新建事件,會有什麼影響呢?

首條日誌寫時間到發現時間之間的日誌將會延遲採集(對於loggie來説,最大延遲在10s左右,因為默認的輪詢時間間隔為10s),但是一旦感知到事件,採集可以很快追上進度。因此新建事件不那麼重要。

  • 文件刪除事件?

當我們沒有及時發現刪除事件,會有什麼影響呢?有3種場景:

  • 文件被刪除後,希望未採集完成的文件繼續採集:這種情況,刪除事件遲到不總要。因為當文件還未採集完,及時發現的刪除事件沒有意義;當文件採集完後,未及時發現的刪除事件僅影響文件句柄釋放延遲。
  • 文件被刪除後,希望儘快釋放磁盤空間:僅僅導致文件句柄釋放延遲,即磁盤空間釋放延遲(大概在10s左右)。
  • 文件被刪除後,希望未採集完的文件給予一定的容忍時間再釋放磁盤空間:這種情況近會導致文件最終釋放延遲的時間=容忍時間+事件遲到時間。

因此,刪除事件不重要。

  • 文件改名事件?

同上,不重要。但是如何得知文件改名以及改成什麼名確實個值得好好思考的問題!(暫不展開)

所以,真正重要的是不活躍的文件寫事件。因此我們的架構調整為:

成果:

  • 節省大量不必要的目錄和文件的系統事件註冊和監聽以及對應監聽產生的大量事件,進一步降低了CPU的損耗。
  • 事件處理這一層去掉了事件合併這一步,因為所有事件都是有效事件,進一步降低了CPU的損耗。

3.1.2 文件讀取

文件讀的快的原則:

  1. 減少磁盤io: 意味着每次需要讀取更多的字節數緩衝在內存,再按行分割處理。
  2. cpu可控: 減少gc和線程調度。

矛盾點:

  • 原則1註定了我們需要重複分配長數組來承載讀取的內容。這意味了很多大對象。
  • 原則2中的gc最害怕的恰恰是轉瞬即逝的大對象。

因此文件讀取的原理為:

  • 複用讀取緩存數組: 重複利用大對象。
  • 讀取字節數: 4k的n倍,充分利用os的page cache。

成果: 讀取性能達到2G/s(本地macbook ssd環境測試)。

3.2 公平性

公平性我們關心的是文件之間的讀取平衡,不要造成文件讀取飢餓(長時間得不到讀取而造成數據延遲)。業界這塊的做法有兩種:

  • 每個文件對應一個讀取線程,有os調度文件讀取保證公平性

    • 優點:能夠保證公平性。
    • 缺點:同時採集的文件數量多的時候cpu消耗太大。
  • 匹配到的文件按照更新事件倒序,按照順序挨個讀取文件

    • 優點:實現簡單。
    • 缺點:無法保證公平性,更新時間早的文件必然會飢餓。

因此loggie實現了基於“時間片”的讀取方式:

成果: 僅僅用一個線程(goroutine)就處理了loggie所有的日誌讀取任務,且最大可能的保證了公平性。

3.3 可靠性

loggie保證了數據的不丟失,實現了at least once保證。對於文件讀取來説,可靠性的實現有一定特殊性:需要保序ack,即我們對於採集點位記錄的持久化前提是當前ack之前的ack全部done。因此為了提高保序ack的性能,我們的這塊的設計進行了一些優化:

對於sink端提交的ack不立即進行持久化,而且進行了雙重壓縮:

  • 在ack chain階段只會提交最尾端的ack到db。
  • db中會對提交的ack再進一步壓縮。

成果: 大大減少了cpu的消耗和ack持久化的磁盤io次數。

3.4 性能對比

對比filebeat,同等情況下,發送至Kafka(單行、單文件、相同發送併發度、無解析場景):

  • 單文件採集對比,Loggie和Filebeat消耗的CPU相比,大概僅為後者的1/4,同時發送吞吐量為後者的1.6~2.6倍。
  • Filebeat的極限吞吐量存在瓶頸,80MB/s後很難提升,而Loggie的極限值更高,多文件採集下能輕鬆達到200MB/s+。

4. 運維治理

基於loggie我們做了很多運維治理,以及應用分析的事情:

4.1 可觀測

根據長期運維、排障經驗歸納提煉的內置指標,可以指導幫助我們快速發現定位問題:

提供了grafana模版,可以一鍵配置:https://github.com/loggie-io/installation/tree/main/prometheus/grafana-dashboard

4.2 完整性

日誌從採集到最終的存儲,鏈路可能比較宂長,中間任何一個環節出問題都可能導致日誌丟失。因此需要有一個日誌完整性校驗的機制來判斷日誌的採集情況。通常我們比較關心兩方面問題:

  • 某個服務的某個日誌數據有沒有丟?
  • 丟的數據能否補?

那如何計算日誌有沒有丟呢?精確完整性計算的核心原理:

  • 計算維度: 機器ip+日誌文件唯一標識

機器ip是確定的,但是如何唯一標識日誌文件呢?

文件名可能重複,因此需要文件名+文件inode(文件標識)。但是inode只在磁盤分區中唯一,因此需要修改為文件名+文件inode(文件標識)+dev(磁盤標識)。但是inode存在複用的情況,如果文件被採集完後被刪除了,inode被複用給一個同名的文件,這樣就變相的造成重複,因此我們需要增加文件內容的前n個字符編碼。最終的計算維度為:機器ip+文件名稱+inode+dev+{fistNBytes}。

  • 近實時計算: 小時級批任務計算

之所以用小計批任務計算,主要有兩個原因:

  • 日誌的完整性計算不需要太實時,因為採集的日誌可能因為種種原因而遲到,實時計算的話很可能會存在太多的數據丟失的情況。而且計算的量級非常大,不適合實時計算。

  • 另一面,不使用更大的時間間隔(例如T+1)計算的原因是,通常日誌都會配置輪轉和清理。如果間隔過大,計算出有丟失的日誌可能因為輪轉和清理被刪除了,那就失去了補數據的機會。

  • 計算邏輯: 日誌行首offset+日誌size=下一行日誌行首offset
  • 補數據: 通過調用loggie原生的文件數據讀取接口獲取(指定offset和讀取的size)。

計算需要的所有metric都附帶在loggie採集的的日誌元數據中。

成果: 簡單易用的平台化展示與補數據。

4.3 日誌延遲

我們計算了日誌在整個鏈路環節中的延遲情況,重點日誌還會根據延遲情況及時的報警:

端到端的延遲計算意義:

  • 幫助我們審視分析鏈路機器資源瓶頸。
  • 指導大促期間的合理擴縮容。
  • 規劃未來增長的日誌所需的容量。

4.4 日誌質量

由於日誌採集後,可能被後續的業務監控報警以及大數據數倉處理分析計算應用,因此日誌的質量變得愈發重要。那如何衡量日誌質量呢?本質上,日誌從非結構化數據被採集後經過一系列處理計算變成了結構化數據,因此我們在日誌結構化的過程中定義了日誌質量分的計算:

  • 日誌切分(字段提取)處理:切分失敗扣1分
  • 字段不存在扣1分
  • 類型轉換處理:轉換失敗扣1分
  • 字段約束失敗扣1分

效果:

日誌質量治理的意義:

  • 量化日誌質量,持續推動服務進化。
  • 為下游日誌的處理極大提高效率。
  • 提高了關鍵日誌的準確數。

4.5 分析應用

基於採集的日誌數據我們做2個重量級的應用,使得日誌的重要程度上升了一個維度,讓日誌真正擁有強烈的業務含義。

4.5.1 業務實時監控

考慮以下需求:

  • 實時監控主站交易是否下跌
  • 實時監控主站UV訪問
  • 實時監控主站的加購、下單情況
  • 實時監控風控的攔截情況
  • 實時監控服務的qps、異常情況
  • ...

通常實現這些需求需要不小的開發週期,但是基於業務實時監控,只需要花5-10分鐘的時間在平台上配置即可展示與報警:

核心原理:

  • 打印對應的業務日誌
  • 在平台配置採集對應的日誌
  • 在平台配置對應日誌的數據模型以及對應指標的計算邏輯(支持明細、聚合等豐富計算)
  • 配置報警規則

成果:

大大降低了業務服務對關鍵業務過程的監控報警的開發成本,通過配置即可擁有業務實時監控報警的能力。

4.5.2 業務全鏈路監控

當前微服務盛行,用户的一個操作可能涉及底下多個微服務調用,考慮以下問題:

  • 如何從業務全局視角觀察對應服務的運行情況?
  • 如何從業務鏈路視角發現上下游佔用比例和依賴?

業務全鏈路監控應運而生:

以交易鏈路為例,從首頁->搜索&推薦->商詳->加購->下單->支付這個主鏈路以及輻射出來的幾條支鏈路,整體的流量是怎麼樣的?調用qps是怎麼樣的?平均rt是什麼樣的?錯誤情況如何?通過業務全鏈路監控一目瞭然。

核心實現原理:

  • 嚴選所有服務默認開啟訪問日誌,默認採集所有服務的訪問日誌
  • 在平台配置業務鏈路流程對應的服務以及接口
  • 配置關鍵鏈路節點的監控報警規則
  • 平台一鍵生成全鏈路實時監控報警

業務全鏈路實時監控的意義非凡,使得我們能夠站在更高的維度,以全局視角審視整個業務流程下的服務調用情況,及時發現鏈路中的薄弱環節以及異常情況,幫助我們優化服務依賴,提升服務穩定性。

5. 結語

嚴選基於loggie的日誌平台化建設,我們一方面在滿足基於日誌的問題分析診斷這一基礎需求之餘,進一步了提升日誌質量、問題排查效率、全鏈路可觀測可運維能力。另一方面,我們擴展能力與豐富應用場景,只需要簡單配置就使得日誌擁有強烈的業務含義與監控語義,使得我們能夠站在更高的維度審視&監控業務鏈路中的異常,提前發現鏈路服務與資源問題。

作者:嚴選技術產品團隊

來源:新一代雲原生日誌架構 - Loggie的設計與實踐