Log Metric Trace

Log

日誌級別

https://github.com/golang/glog,是 google 提供的一個不維護的日誌庫, glog 有其他語言的一些版本,對我當時使用 log 庫有很大的影響。它包含如下日誌級別:

  • Info
  • Warning
  • Error
  • Fatal(會中斷程序執行)

還有類似 log4go,loggo,zap 等其他第三方日誌庫,他們還提供了設置日誌級別的可見行,一般提供日誌級別:

  • Trace
  • Debug
  • Info
  • Warning
  • Error
  • Critical
Warning

沒人看警告,因為從定義上講,沒有什麼出錯。也許將來會出問題,但這聽起來像是別人的問題。我們盡可能的消除警告級別,它要麼是一條信息性消息,要麼是一個錯誤。 我們參考 Go 語言設計額哲學,所有警告都是錯誤,其他語言的 warning 都可以忽略,除非 IDE 或者在 CICD 流程中強制他們為 error,然後逼著程序員們盡可能去消除。 同樣的,如果想要最終消除 warning 可以記錄為 error,讓代碼作者重視起來。

Fatal

記錄消息後,直接調用 os.Exit(1),這意味著:

  • 在其他 goroutine defer 語句不會被執行;
  • 各種 buffers 不會被 flush,包括日誌的;
  • 臨時文件或者目錄不會被移除; 不要使用 fatal 記錄日誌,而是向調用者返回錯誤。 如果錯誤一直持續到 main.main。 main.main 那就是在退出之前做處理任何清理操作的正確位置。

建議設計日誌庫就不使用了

Error

也有很多人,在錯誤發生的地方要立馬記錄日誌,尤其要使用 error 級別記錄。

  • 處理 error;
  • 把 error 拋給調用者,在頂部打印日誌; planA()出錯後, 執行 planB(). 這裡產生了降級行為,本質屬於有損服務,我更傾向在這裡使用 Warning。

如果您選擇通過日誌記錄來處理錯誤,那麼根據定義,它不再是一個錯誤 — 您已經處理了它。記錄錯誤的行為會處理錯誤,因此不再適合將其記錄為錯誤。

Debug

相信只有兩件事你應該記錄:

  • 開發人員在開發或調試軟件時關心的事情。
  • 用戶在使用軟件時關心的事情。 顯然,它們分別是調試和信息級別。

log.Info 只需將該行寫入日誌輸出。不應該有關閉它的選項,因為用戶只應該被告知對他們有用的事情。 如果發生了一個無法處理的錯誤,它就會拋出到 main.main。 main.main 程序終止的地方。在最後的日誌消息前面插入 fatal 前綴,或者直接寫入 os.Stderr。 log.Debug,是完全不同的事情。它由開發人員或支持工程師控制。在開發過程中,調試語句應該是豐富的,而不必求助於 trace 或 debug2(您知道自己是誰)級別。日誌包應該支持細粒度控制,以啟用或禁用調試,並且只在包或更精細的範圍內啟用或禁用調試語句。

我們如何設計和思考的:https://github.com/go-kratos/kratos/tree/v2.0.0/log

Logger

在 package 使用的時候

package foo
import “mylogger”
var log = mylogger.GetLogger(“github.com/project/foo”)
  • foo 耦合了 mylogger
  • 所有使用 foo 的其他庫,被透明依賴了 mylogger

當我們使用 kit 時候

package foo
import "github.com/pkg/log"
type T struct {
        logger log.Logger
}

延遲需要打日誌的類型與日誌的實際類型之間的綁定。

日誌選型

一個完整的集中式日誌系統,需要包含以下幾個主要特點:

  • 收集-能夠採集多種來源的日誌數據;
  • 傳輸-能夠穩定的把日誌數據傳輸到中央系統;
  • 存儲-如何存儲日誌數據;
  • 分析-可以支持 UI 分析;
  • 警告-能夠提供錯誤報告,監控機制;

開源界鼎鼎大名 ELK stack,分別表示: Elasticsearch , Logstash, Kibana , 它們都是開源軟件。 新增了一個 FileBeat,它是一個輕量級的日誌收集處理工具(Agent),Filebeat 佔用資源少,適合於在各個服務器上蒐集日誌後傳輸給 Logstash,官方也推薦此工具。

此架構由 Logstash 分佈於各個節點上蒐集相關日誌、數據,並經過分析、過濾後發送給遠端服務器上的 Elasticsearch 進行存儲。

Elasticsearch 將數據以分片的形式壓縮存儲並提供多種 API 供用戶查詢,操作。 用戶亦可以更直觀的通過配置 Kibana Web方便的對日誌查詢,並根據數據生成報表。

因為 logstash 屬於 server 角色,必然出現流量集中式的熱點問題,因此我們不建議使用這種部署方式,同時因為 還需要做大量 match 操作(格式化日誌),消耗的 CPU 也很多,不利於 scale out。

此種架構引入了消息隊列機制,位於各個節點上的 Logstash Agent 先將數據/日誌傳遞給 Kafka,並將隊列中消息或數據間接傳遞給 Logstash,Logstash 過濾、分析後將數據傳遞給Elasticsearch 存儲。最後由 Kibana 將日誌和數據呈現給用戶。因為引入了 Kafka,所以即使遠端 Logstash server 因故障停止運行,數據將會先被存儲下來,從而避免數據丟失。 更進一步的: 將收集端 logstash 替換為 beats,更靈活,消耗資源更少,擴展性更強。

日誌系統:設計目標

  • 接入方式收斂;
  • 日誌格式規範;
  • 日誌解析對日誌系統透明;
  • 系統高吞吐、低延遲;
  • 系統高可用、容量可擴展、高可運維性;

日誌系統:格式規範

JSON作為日誌的輸出格式:

  • time: 日誌產生時間,ISO8601格式;
  • level: 日誌等級,ERRORWARNINFODEBUG
  • app_id: 應用id,用於標示日誌來源;
  • instance_id: 實例 id,用於區分同一應用不同實例,即 hostname;

日誌系統:設計與實現

日誌從產生到可檢索,經歷幾個階段:

  • 生產 & 採集
  • 傳輸 & 切分
  • 存儲 & 檢索

日誌系統:採集

logstash:

  • 監聽tcp/udp
  • 適用於通過網絡上報日誌的方式

filebeat:

  • 直接採集本地生成的日誌文件
  • 適用於日誌無法定制化輸出的應用

logagent:

  • 物理機部署,監聽unixsocket
  • 日誌系統提供各種語言SDK
  • 直接讀取本地日誌文件

日誌系統:logagent 設計

日誌系統: 傳輸

基於Flume + Kafka 統一傳輸平台 基於LogID做日誌分流:

  1. 一般級別
  2. 低級別
  3. 高級別(ERROR)

現在替換為 Flink + Kafka 的實現方式

日誌系統: 切分

從kafka消費日誌,解析日誌,寫入elasticsearch

bili-index: 自研,golang開發,邏輯簡單,性能 高, 可定制化方便。

  • 日誌規範產生的日誌(log agent收集)

logstash: es官方組件,基於jruby開發,功能強大, 資源消耗高,性能低。

  • 處理未按照日誌規範產生的日誌(filebeat、logstash 收集),需配置各種日誌解析規則。

日誌系統: 存儲和檢索

elasticsearch多集群架構:

  • 日誌分級、高可用

單數據集群內: master node + data node(hot/stale) + client node

  • 每日固定時間進行熱->冷遷移
  • Index 提前一天創建,基於 template 進行mapping 管理
  • 檢索基於 kibana

日誌系統: 文件

使用自定義協議,對 SDK 質量、版本升級都有比較高的要求,因此我們長期會使用“本地文件”的方案實現:

  • 採集本地日誌文件:位置不限,容器內 or 物理機
  • 配置自描述:不做中心化配置,配置由 app/paas 自身提供,agent 讀取配置並生效
  • 日誌不重不丟:多級隊列,能夠穩定地處理日誌收集過程中各種異常
  • 可監控:實時監控運行狀態
  • 完善的自我保護機制:限制自身對於宿主機資源的消耗,限制發送速度

日誌系統: 容器日誌採集

容器內應用日誌採集: 基於 overlay2,直接從物理機上查找對應日誌文件

Trace

鏈路追蹤: 設計目標

  • 無處不在的部署
  • 持續的監控
  • 低消耗
  • 應用級的透明
  • 延展性
  • 低延遲

鏈路追蹤: Dapper

參考 Google Dapper 論文實現,為每個請求都生成一個全局唯一的 traceid,端到端透傳到上下游所有節點, 每一層生成一個 spanid, 通過traceid 將不同系統孤立的調用日誌和異常信息串聯一起, 通過 spanid 和 level 表達節點的父子關係。 核心概念:

  • Tree
  • Span
  • Annotation

鏈路追蹤: 調用練

在跟踪樹結構中,樹節點是整個架構的基本單元,而每一個節點又是對 span 的引用。 雖然 span 在日誌文件中只是簡單的代表 span 的開始和結束時間,他們在整個樹形結構中卻是相對獨立的。 核心概念:

  • TraceID
  • SpanID
  • ParentID
  • Family & Title

鏈路追踪:追踪信息

  • 追踪信息包含時間戳、事件、方法名(Family+Title)、註釋(TAG/Comment)。
  • 客戶端和服務器上的時間戳來自不同的主機,我們必須考慮到時間偏差,RPC 客戶端發送一個請求之後,服務器端才能接收到,對於響應也是一樣的(服務器先響應,然後客戶端才能接收到這個響應)。這樣一來,服務器端的 RPC 就有一個時間戳的一個上限和下限。

鏈路追踪:植入點

Dapper 可以以對應用開發者近乎零浸入的成本對分佈式控制路徑進行跟踪, 幾乎完全依賴於基於少量通用組件庫的改造。 如下: 當一個線程在處理跟踪控制路徑的過程中, Dapper 把這次跟踪的上下文的在 ThreadLocal中進行存儲, 在 Go 語言中,約定每個方法首參數為 context(上下文) 覆蓋通用的中間件&通訊框架、不限於:redis、memcache、rpc、http、database、queue。

鏈路追踪: 架構圖

鏈路追踪: 跟蹤消耗

處理跟踪消耗:

  • 正在被監控的系統在生成追踪和收集追踪數據的消耗導致系統性能下降,
  • 需要使用一部分資源來存儲和分析跟踪數據,是Dapper性能影響中最關鍵的部分:
    • 因為收集和分析可以更容易在緊急情況下被關閉,ID生成耗時、創建Span等;
    • 修改agent nice值,以防在一台高負載的服務器上發生cpu競爭;

採樣: 如果一個顯著的操作在系統中出現一次,他就會出現上千次,基於這個事情我們不全量收集數據。

有意思的論文:Uncertainty in Aggregate Estimates from Sampled Distributed Traces

鏈路追踪: 跟蹤採樣

固定採樣,1/1024:

這個簡單的方案是對我們的高吞吐量的線上服務來說是非常有用,因為那些感興趣的事件(在大吞吐量的情況下)仍然很有可能經常出現,並且通常足以被捕捉到。 然而,在較低的採樣率和較低的傳輸負載下可能會導致錯過重要事件,而想用較高的採樣率就需要能接受的性能損耗。 對於這樣的系統的解決方案就是覆蓋默認的採樣率,這需要手動干預的,這種情況是我們試圖避免在 Dapper 中出現的。

應對積極採樣:

我們理解為單位時間期望採集樣本的條目,在高 QPS 下,採樣率自然下降,在低 QPS 下,採樣率自然增加;比如1s內某個接口採集1條。

二級採樣:

容器節點數量多,即使使用積極採樣仍然會導致採樣樣本非常多,所以需要控制寫入中央倉庫的數據的總規模, 利用所有 span 都來自一個特定的跟踪並分享同一個 traceid 這個事實,雖然這些 span 有可能橫跨了數千個主機。 對於在收集系統中的每一個 span,我們用hash算法把 traceid 轉成一個標量Z ,這裡0<=Z<=1,我們選擇了運行期採樣率, 這樣就可以優雅的去掉我們無法寫入到倉庫中的多餘數據,我們還可以通過調節收集系統中的二級採樣率係數來調整這個運行期採樣率, 最終我們通過後端存儲壓力把策略下發給 agent採集系統,實現精準的二級採樣。

下游採樣:

越被依賴多的服務,網關層使用積極採樣以後, 對於 downstream 的服務採樣率仍然很高。

鏈路追踪: API

搜索: 按照 Family(服務名)、Title(接口)、時間、調用者等維度進行搜索

詳情: 根據單個 traceid,查看整體鏈路信息,包含 span、level 統計,span 詳情,依賴的服務、組件信息等;

全局依賴圖: 由於服務之間的依賴是動態改變的,所以不可能僅從配置信息上推斷出所有這些服務之間的依賴關係,能夠推算出任務各自之間的依賴,以及任務和其他軟件組件之間的依賴。

依賴搜索: 搜索單個服務的依賴情況,方便我們做“異地多活”時候來全局考慮資源的部署情況,以及區分服務是否屬於多活範疇,也可以方便我們經常性的梳理依賴服務和層級來優化我們的整體架構可用性。

推斷環依賴: 一個複雜的業務架構,很難避免全部是層級關係的調用,但是我們要盡可能保證一點:調用棧永遠向下,即:不產生環依賴。

鏈路追踪:經驗&優化

性能優化:

  1. 不必要的串行調用
  2. 緩存讀放大
  3. 數據庫寫放大
  4. 服務接口聚合調用

異常日誌系統集成: 如果這些異常發生在 Dapper 跟踪採樣的上下文中,那麼相應的 traceid 和 spanid 也會作為元數據記錄在異常日誌中。 異常監測服務的前端會提供一個鏈接,從特定的異常信息的報告直接導向到他們各自的分佈式跟踪;

用戶日誌集成: 在請求的頭中返回 traceid,當用戶遇到故障或者上報客服我們可以根據 traceid 作為整個請求鏈路的關鍵字, 再根據接口級的服務依賴接口所涉及的服務並行搜索 ES Index,聚合排序數據,就比較直觀的診斷問題了;

容量預估: 根據入口網關服務,推斷整體下游服務的調用扇出來精確預估流量再各個系統的佔比;

網絡熱點&易故障點: 我們內部 RPC 框架還不夠統一,以及基礎庫的組件部分還沒解決拿到應用層協議大小, 如果我們收集起來,可以很簡單的實現流量熱點、機房熱點、異常流量等情況。 同理容易失敗的 span,很容易統計出來,方便我們辨識服務的易故障點;

opentraceing: 標準化的推廣,上面幾個特性,都依賴 span TAG 來進行計算, 因此我們會逐步完成標準化協議,也更方便我們開源,而不是一個內部“特殊系統”;

監控

Monitoring:

  • 延遲(latency)、流量(QPS)、錯誤、飽和度
  • 長尾問題
  • 依賴資源 (Client/Server 's view)

opentracing (Google Dapper):

  • jaeger
  • zipkin

Logging:

  • traceid關聯

Metric:

  • Prometheus + Granfana

日誌級別 涉及到 net、cache、db、rpc 等資源類型的基礎庫,首先監控維度4個黃金指標:

  • 延遲(耗時,需要區分正常還是異常)
  • 流量(需要覆蓋來源,即:caller)
  • 錯誤(覆蓋錯誤碼或者 HTTP Status Code)
  • 飽和度(服務容量有多“滿”)

系統層面:

  • CPU,Memory,IO,Network,TCP/IP狀態等,FD(等其他),Kernel:Context Switch
  • Runtime:各類 GC、Mem 內部狀態等

監控 線上打開 Profiling 的端口; 使用服務發現找到節點信息,以及提供快捷的方式快速可以 WEB 化查看進程的 Profiling 信息(火焰圖等); watchdog,使用內存、CPU等信號量觸發自動採集;

Reference

© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:53

results matching ""

    No results matching ""

    results matching ""

      No results matching ""