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

Loggie萌芽于網(wǎng)易嚴選業(yè)務的實際需求,成長于嚴選與數(shù)帆的長期共建,持續(xù)發(fā)展于網(wǎng)易數(shù)帆與網(wǎng)易傳媒、中國工商銀行的緊密協(xié)作。廣泛的生態(tài),使得項目能夠基于業(yè)務需求不斷完善、成熟。目前已經(jīng)開源:https://github.com/loggie-io/loggie。
1、背景
嚴選日志平臺初期,使用filebeat采集云內(nèi)日志,用flume采集云外日志。期間經(jīng)歷了一段痛苦的運維排障時期,被問的最多的幾個問題:
- 某條日志為何沒有采集?
- 某條日志為何重復采集了?
- 能否將漏采集的那條日志補采集?
- 某個日志文件為何沒有采集?
- 某個日志文件的采集速度怎么這么慢(延遲超過30s)?
- 服務重新發(fā)布后,之前的日志怎么沒有了?
- …
而且同時維護了2套日志采集agent,維護成本高。不管是filebeat還是flume都存在以下幾點嚴重的問題:
- 采集性能低:大促時間,部分節(jié)點的日志打印速度超過100MB/s,然而filebeat的采集性能上限只有80MB/s左右,flume更差一點。
- 資源占用高:filebeat單節(jié)點采集文件超過100個,cpu使用率超800%;flume空跑內(nèi)存就得占用200MB+,大促期間不得不開啟限流以避免影響核心業(yè)務系統(tǒng)。
- 擴展性差:fliebeat復雜的架構以及單output設計無法滿足多變的業(yè)務需求。
同時也調(diào)研其他開源的日志采集agent,或多或少都存在上述問題,且都沒有足夠的可觀測性和運維手段(工具)來幫助運維排障,更不用說完整的日志解決方案。
因此我們走向了自研的道路。
loggie在2022年初已開源:https://github.com/loggie-io/loggie
歡迎大家使用,與社區(qū)一起成長!
2、架構設計

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

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

pipeline的簡潔設計使得我們的擴展極其便捷。在概念上,一個pipeline只有4種組件:source、queue、sink、interceptor,而且interceptor不是必須的。越少的概念,擴展者者就有越少的學習成本。并且,為了進一步提高擴展性,pipeline中的所有組件都抽象為component,所有的組件擁有一致的生命周期與實現(xiàn)方式。不僅方便了擴展,也方便了loggie對組件的統(tǒng)一管理。
隔離性與reload
基于pipeline,我們實現(xiàn)了配置熱更新,組件熱加載。reloader與discovery組件可以基于K8S CRD、http監(jiān)聽等方式(預留接口,可以對接例如zookeeper、consul、apollo等配置中心),以pipeline維度進行reload。因此,在保證了reload能力的同時仍然滿足了pipeline隔離的目的。
功能增強:interceptor
interceptor在loggie中是一個可選的組件,卻在loggie中扮演著非常重要的角色。loggie中絕大多數(shù)的增強功能都是基于interceptor實現(xiàn)的,例如限流、背壓、日志切分、encode&decode、字符編碼、結構化等。用戶可以根據(jù)實際情況選擇對應的interceptor提升loggie的適應能力。
完整內(nèi)置interceptor:https://loggie-io.github.io/docs/reference/pipelines/interceptor/overview/。
3、日志采集
對于一個日志采集agent來說,通常需要重點關心以下3點的設計與實現(xiàn):
- 高效采集:高效指的是高性能的同時低能耗,即如何采集的快服務器資源占用有小。
- 公平性:例如寫入快的文件不能影響寫入慢的文件采集、最近更新的文件不能影響之前更新的文件的采集,刪除文件的合理采集與釋放。
- 可靠性:日志不丟失。包括進程崩潰重啟、服務發(fā)布&遷移&容器漂移、下游阻塞等情況。
(1)高效采集
日志采集,我們關心的是這么幾個問題:
- 如何及時發(fā)現(xiàn)新創(chuàng)建的文件?
- 如何及時發(fā)現(xiàn)最新的寫入?
- 如何快速讀取文件?
這其實是兩方面的問題:
- 事件感知:及時發(fā)現(xiàn)文件事件。例如文件新建、刪除、寫入。
- 文件讀取:高效讀取文件內(nèi)容。盡可能快的讀取文件內(nèi)容,減少磁盤io,cpu可控。
事件感知
如何做到文件事件感知呢?業(yè)界有兩種做法:
- 定時輪詢:定時檢查目錄文件狀態(tài)。
- OS事件通知:注冊OS文件事件,由OS通知具體的文件事件。
兩種方式各有利弊:
- 定時輪詢:
- 優(yōu)點:實現(xiàn)簡單,安全可靠。
- 缺點:輪詢的時間間隔不太好確定,間隔太短耗cpu,間隔太長發(fā)現(xiàn)事件就太慢。
- OS事件通知:
- 優(yōu)點:能夠第一時間發(fā)現(xiàn)文件事件
- 缺點:
- 實現(xiàn)成本高(不同OS的實現(xiàn)方式不同,需要適配)。
- 存在bug:例如在最常用的linux系統(tǒng)中存在一些bug(https://man7.org/linux/man-pages/man7/inotify.7.html) ,其中影響最大的幾點:通知丟失、注冊數(shù)量有上限、文件改名事件無法通知改名后的文件名。
- 使用有限制:例如無法對通過 NFS(Network File System) 掛載的網(wǎng)盤&云盤生效,無法對 FUSE(filesystem in userspace) 生效等。
因此loggie結合兩者共同實現(xiàn)了一個安全可靠,卻又能及時感知事件的方案:

同時啟用定時輪詢和OS通知,使用OS通知然后搭配一個相對較長(loggie默認為10s)的定時輪詢,將兩者發(fā)現(xiàn)的事件進行合并以減少重復處理,這樣就能同時兼具兩者的優(yōu)點。
但是實際測試下來,我們發(fā)現(xiàn)了cpu占用上升,分析原因:
- OS事件過多:特別是寫文件的時候,對應有兩個os事件(chmod+write),一旦文件寫得頻繁,os的事件將非常多,loggie疲于處理系統(tǒng)事件。
所以,我們重新分析,什么樣的事件是我們真正關心并需要及時感知的呢?
- 文件寫事件?
當我們無法及時發(fā)現(xiàn)文件寫事件,會有什么影響呢?有兩種情況:
- 如果這個文件正處于采集中(持有文件句柄),那這個文件的寫事件沒有影響。因為正在讀這個文件,后續(xù)的寫入理所當然能被讀到。
- 如果這個文件處于不活躍狀態(tài)(即文件已經(jīng)讀取到了末尾,并且一定時間內(nèi)沒有發(fā)現(xiàn)新內(nèi)容,甚至文件的文件句柄被釋放了),這個情況我們希望能及時感知文件的寫事件以方便我們及時采集最新的寫入內(nèi)容。
因此,重要的是“不活躍”文件的寫事件。
- 文件新建(滾動)事件?
當我們沒有及時發(fā)現(xiàn)新建事件,會有什么影響呢?
首條日志寫時間到發(fā)現(xiàn)時間之間的日志將會延遲采集(對于loggie來說,最大延遲在10s左右,因為默認的輪詢時間間隔為10s),但是一旦感知到事件,采集可以很快追上進度。因此新建事件不那么重要。
- 文件刪除事件?
當我們沒有及時發(fā)現(xiàn)刪除事件,會有什么影響呢?有3種場景:
- 文件被刪除后,希望未采集完成的文件繼續(xù)采集:這種情況,刪除事件遲到不總要。因為當文件還未采集完,及時發(fā)現(xiàn)的刪除事件沒有意義;當文件采集完后,未及時發(fā)現(xiàn)的刪除事件僅影響文件句柄釋放延遲。
- 文件被刪除后,希望盡快釋放磁盤空間:僅僅導致文件句柄釋放延遲,即磁盤空間釋放延遲(大概在10s左右)。
- 文件被刪除后,希望未采集完的文件給予一定的容忍時間再釋放磁盤空間:這種情況近會導致文件最終釋放延遲的時間=容忍時間+事件遲到時間。
因此,刪除事件不重要。
- 文件改名事件?
同上,不重要。但是如何得知文件改名以及改成什么名確實個值得好好思考的問題?。〞翰徽归_)
所以,真正重要的是不活躍的文件寫事件。因此我們的架構調(diào)整為:

成果:
- 節(jié)省大量不必要的目錄和文件的系統(tǒng)事件注冊和監(jiān)聽以及對應監(jiān)聽產(chǎn)生的大量事件,進一步降低了CPU的損耗。
- 事件處理這一層去掉了事件合并這一步,因為所有事件都是有效事件,進一步降低了CPU的損耗。
文件讀取
文件讀的快的原則:
- 減少磁盤io:意味著每次需要讀取更多的字節(jié)數(shù)緩沖在內(nèi)存,再按行分割處理。
- cpu可控:減少gc和線程調(diào)度。
矛盾點:
- 原則1注定了我們需要重復分配長數(shù)組來承載讀取的內(nèi)容。這意味了很多大對象。
- 原則2中的gc最害怕的恰恰是轉(zhuǎn)瞬即逝的大對象。

因此文件讀取的原理為:
- 復用讀取緩存數(shù)組:重復利用大對象。
- 讀取字節(jié)數(shù):4k的n倍,充分利用os的page cache。
成果: 讀取性能達到2G/s(本地macbook ssd環(huán)境測試)。
(2)公平性
公平性我們關心的是文件之間的讀取平衡,不要造成文件讀取饑餓(長時間得不到讀取而造成數(shù)據(jù)延遲)。業(yè)界這塊的做法有兩種:
- 每個文件對應一個讀取線程,有os調(diào)度文件讀取保證公平性。
- 優(yōu)點:能夠保證公平性。
- 缺點:同時采集的文件數(shù)量多的時候cpu消耗太大。
- 匹配到的文件按照更新事件倒序,按照順序挨個讀取文件。
- 優(yōu)點:實現(xiàn)簡單。
- 缺點:無法保證公平性,更新時間早的文件必然會饑餓。
因此loggie實現(xiàn)了基于“時間片”的讀取方式:

成果: 僅僅用一個線程(goroutine)就處理了loggie所有的日志讀取任務,且最大可能的保證了公平性。
(3)可靠性
loggie保證了數(shù)據(jù)的不丟失,實現(xiàn)了at least once保證。對于文件讀取來說,可靠性的實現(xiàn)有一定特殊性:需要保序ack,即我們對于采集點位記錄的持久化前提是當前ack之前的ack全部done。因此為了提高保序ack的性能,我們的這塊的設計進行了一些優(yōu)化:

對于sink端提交的ack不立即進行持久化,而且進行了雙重壓縮:
- 在ack chain階段只會提交最尾端的ack到db。
- db中會對提交的ack再進一步壓縮。
成果: 大大減少了cpu的消耗和ack持久化的磁盤io次數(shù)。
(4)性能對比
對比filebeat,同等情況下,發(fā)送至Kafka(單行、單文件、相同發(fā)送并發(fā)度、無解析場景):

- 單文件采集對比,Loggie和Filebeat消耗的CPU相比,大概僅為后者的1/4,同時發(fā)送吞吐量為后者的1.6~2.6倍。
- Filebeat的極限吞吐量存在瓶頸,80MB/s后很難提升,而Loggie的極限值更高,多文件采集下能輕松達到200MB/s+。
4、運維治理
基于loggie我們做了很多運維治理,以及應用分析的事情:
(1)可觀測
根據(jù)長期運維、排障經(jīng)驗歸納提煉的內(nèi)置指標,可以指導幫助我們快速發(fā)現(xiàn)定位問題:


提供了grafana模版,可以一鍵配置:https://github.com/loggie-io/installation/tree/main/prometheus/grafana-dashboard。
(2)完整性
日志從采集到最終的存儲,鏈路可能比較冗長,中間任何一個環(huán)節(jié)出問題都可能導致日志丟失。因此需要有一個日志完整性校驗的機制來判斷日志的采集情況。通常我們比較關心兩方面問題:
- 某個服務的某個日志數(shù)據(jù)有沒有丟?
- 丟的數(shù)據(jù)能否補?
那如何計算日志有沒有丟呢?精確完整性計算的核心原理:
- 計算維度:機器ip+日志文件唯一標識
機器ip是確定的,但是如何唯一標識日志文件呢?
文件名可能重復,因此需要文件名+文件inode(文件標識)。但是inode只在磁盤分區(qū)中唯一,因此需要修改為文件名+文件inode(文件標識)+dev(磁盤標識)。但是inode存在復用的情況,如果文件被采集完后被刪除了,inode被復用給一個同名的文件,這樣就變相的造成重復,因此我們需要增加文件內(nèi)容的前n個字符編碼。最終的計算維度為:機器ip+文件名稱+inode+dev+{fistNBytes}。
- 近實時計算:小時級批任務計算
之所以用小計批任務計算,主要有兩個原因:
- 日志的完整性計算不需要太實時,因為采集的日志可能因為種種原因而遲到,實時計算的話很可能會存在太多的數(shù)據(jù)丟失的情況。而且計算的量級非常大,不適合實時計算。
- 另一面,不使用更大的時間間隔(例如T+1)計算的原因是,通常日志都會配置輪轉(zhuǎn)和清理。如果間隔過大,計算出有丟失的日志可能因為輪轉(zhuǎn)和清理被刪除了,那就失去了補數(shù)據(jù)的機會。
- 計算邏輯:日志行首offset+日志size=下一行日志行首offset
- 補數(shù)據(jù):通過調(diào)用loggie原生的文件數(shù)據(jù)讀取接口獲?。ㄖ付╫ffset和讀取的size)。
計算需要的所有metric都附帶在loggie采集的的日志元數(shù)據(jù)中。
成果: 簡單易用的平臺化展示與補數(shù)據(jù)。



(3)日志延遲
我們計算了日志在整個鏈路環(huán)節(jié)中的延遲情況,重點日志還會根據(jù)延遲情況及時的報警:

端到端的延遲計算意義:
- 幫助我們審視分析鏈路機器資源瓶頸。
- 指導大促期間的合理擴縮容。
- 規(guī)劃未來增長的日志所需的容量。
(4)日志質(zhì)量
由于日志采集后,可能被后續(xù)的業(yè)務監(jiān)控報警以及大數(shù)據(jù)數(shù)倉處理分析計算應用,因此日志的質(zhì)量變得愈發(fā)重要。那如何衡量日志質(zhì)量呢?本質(zhì)上,日志從非結構化數(shù)據(jù)被采集后經(jīng)過一系列處理計算變成了結構化數(shù)據(jù),因此我們在日志結構化的過程中定義了日志質(zhì)量分的計算:
- 日志切分(字段提?。┨幚恚呵蟹质】?分
- 字段不存在扣1分
- 類型轉(zhuǎn)換處理:轉(zhuǎn)換失敗扣1分
- 字段約束失敗扣1分
效果:


日志質(zhì)量治理的意義:
- 量化日志質(zhì)量,持續(xù)推動服務進化。
- 為下游日志的處理極大提高效率。
- 提高了關鍵日志的準確數(shù)。
(5)分析應用
基于采集的日志數(shù)據(jù)我們做2個重量級的應用,使得日志的重要程度上升了一個維度,讓日志真正擁有強烈的業(yè)務含義。
業(yè)務實時監(jiān)控
考慮以下需求:
- 實時監(jiān)控主站交易是否下跌
- 實時監(jiān)控主站UV訪問
- 實時監(jiān)控主站的加購、下單情況
- 實時監(jiān)控風控的攔截情況
- 實時監(jiān)控服務的qps、異常情況
- …
通常實現(xiàn)這些需求需要不小的開發(fā)周期,但是基于業(yè)務實時監(jiān)控,只需要花5-10分鐘的時間在平臺上配置即可展示與報警:

核心原理:
- 打印對應的業(yè)務日志
- 在平臺配置采集對應的日志
- 在平臺配置對應日志的數(shù)據(jù)模型以及對應指標的計算邏輯(支持明細、聚合等豐富計算)
- 配置報警規(guī)則
成果:
大大降低了業(yè)務服務對關鍵業(yè)務過程的監(jiān)控報警的開發(fā)成本,通過配置即可擁有業(yè)務實時監(jiān)控報警的能力。
業(yè)務全鏈路監(jiān)控
當前微服務盛行,用戶的一個操作可能涉及底下多個微服務調(diào)用,考慮以下問題:
- 如何從業(yè)務全局視角觀察對應服務的運行情況?
- 如何從業(yè)務鏈路視角發(fā)現(xiàn)上下游占用比例和依賴?
業(yè)務全鏈路監(jiān)控應運而生:

以交易鏈路為例,從首頁->搜索&推薦->商詳->加購->下單->支付這個主鏈路以及輻射出來的幾條支鏈路,整體的流量是怎么樣的?調(diào)用qps是怎么樣的?平均rt是什么樣的?錯誤情況如何?通過業(yè)務全鏈路監(jiān)控一目了然。
核心實現(xiàn)原理:

- 嚴選所有服務默認開啟訪問日志,默認采集所有服務的訪問日志
- 在平臺配置業(yè)務鏈路流程對應的服務以及接口
- 配置關鍵鏈路節(jié)點的監(jiān)控報警規(guī)則
- 平臺一鍵生成全鏈路實時監(jiān)控報警
業(yè)務全鏈路實時監(jiān)控的意義非凡,使得我們能夠站在更高的維度,以全局視角審視整個業(yè)務流程下的服務調(diào)用情況,及時發(fā)現(xiàn)鏈路中的薄弱環(huán)節(jié)以及異常情況,幫助我們優(yōu)化服務依賴,提升服務穩(wěn)定性。
5、結語
嚴選基于loggie的日志平臺化建設,我們一方面在滿足基于日志的問題分析診斷這一基礎需求之余,進一步了提升日志質(zhì)量、問題排查效率、全鏈路可觀測可運維能力。另一方面,我們擴展能力與豐富應用場景,只需要簡單配置就使得日志擁有強烈的業(yè)務含義與監(jiān)控語義,使得我們能夠站在更高的維度審視&監(jiān)控業(yè)務鏈路中的異常,提前發(fā)現(xiàn)鏈路服務與資源問題。





























