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

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

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/...

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

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/d...

3. 日誌採集

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

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

3.1 高效採集

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

  • 如何及時發現新建立的檔案?
  • 如何及時發現最新的寫入?
  • 如何快速讀取檔案?

這其實是兩方面的問題:

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

3.1.1 事件感知

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

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

兩種方式各有利弊:

  • 定時輪詢:

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

    • 優點:能夠第一時間發現檔案事件
    • 缺點:

      • 實現成本高(不同OS的實現方式不同,需要適配)。
      • 存在bug:例如在最常用的linux系統中存在一些bug(https://man7.org/linux/man-pa...) ,其中影響最大的幾點:通知丟失、註冊數量有上限、檔案改名事件無法通知改名後的檔名。
      • 使用有限制:例如無法對透過 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/...

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的設計與實踐

相關文章