論程式的可觀測性和自動化監控

2023/10/30 dev 共 2535 字,約 8 分鐘

Introduction

對於工程師而言,程式上線後最大的痛苦大概就是發生了異常狀況,又沒有辦法即時修改程式增加log來dump上下文資料或分析程式執行路徑,看著單調的錯誤訊息猜謎。要想未卜先知的猜測可能發生異常的地方,事先加上log卻又太過雜亂且發散。藉由可觀測性的概念可以協助我們理清思路,並埋設正確的觀測點,來輔助監控和發現異常。

在軟體工程中,可觀測性是指你能夠有效地監視和記錄應用程式的運行狀態和問題,以便更容易地進行故障排除和性能優化。

可觀測性的 logging、metric 和 tracing 是軟體系統中用於監控和了解系統運作的三個重要方面:

  1. 日誌 (Logging):
    • 日誌是用於記錄應用程式的執行期事件和資訊的紀錄。
    • 它們通常包括錯誤訊息、警告、追蹤性訊息等。
    • 日誌可以用於追蹤程式的執行、故障排除和記錄重要事件。
  2. 指標 (Metrics):
    • 指標是用於量化和度量系統性能的數據。
    • 它們通常包括 CPU 使用率、記憶體使用量、請求數量等。
    • 指標可用於實時監控和了解系統的整體健康狀況。
  3. 追蹤 (Tracing):
    • 追蹤是用於追蹤應用程式中不同元件之間的操作和相互作用的過程。
    • 它通常包括了解請求在系統中的流程、耗時操作等。
    • 追蹤可用於分析和優化應用程式的效能和工作流程。

這些可觀測性的方法通常在大型複雜的軟體系統中使用,以幫助開發者監控、診斷和改進系統。在使用 Go 語言開發應用程式時,你可以使用相應的庫和工具來實現這些可觀測性功能,例如使用 Logrus 進行日誌記錄、Prometheus 進行指標監控、以及 OpenTelemetry 進行分布式追蹤。

這三者之間是存在交集的,意味著某個異常或問題或許可以透過 logging 和 metrics 兩種方式呈現,具體取決於你想要監控和了解的資訊。

舉例來說:logging像是回報高速公路上發生的每個車禍。metrics像是偵測每個區間的車流量。他們負責的面向雖然不一樣,但在功能上都同樣可以呈現車流不順暢的狀況。我們也有可能同時觀察兩種資訊,來更精確的了解目前發生的狀況。

因為我幾乎沒有使用Tracing來觀測系統,大多是以logging和metrics結合為主,以下就只寫前兩個。

logging

現在的log形式多半是以結構化的方式儲存。白話來說就是存成一個json物件,物件內可能包含時間(timestamp)、檔案名稱(file)、行號(line)、函式名稱(func)、錯誤訊息(msg)、程度(severity)和其他輔助資訊。輸出時再根據格式format成單行的純文字檔。

其中我們最常用來觀察程式執行狀況的是程度或等級(severity or level),也就是:error, warning, info…,通常的共識是:

  • error: 有異常需要解決
  • warning: 可能有問題,但沒有出現error,那就不管
  • info: 無視

將log以統一的方式進行程度分級是有必要的,特別是維運時不一定有辦法得知該訊息的嚴重程度,誤植就會帶來噪音。我的分類方法是:

  • fatal: 和error相同等級,但在印出fatal後程式會自行終止
  • error: 程式無法自行處理的異常。如連不上資料庫、硬碟空間已滿、權限不足等等…。沒有自行終止可能是因為1. 該程式可能正在服務多個請求,而其他請求仍然可以正常執行。2. 程式預期經過重試後有可能恢復運作。
  • warning: 程式遇到一個可以忽略的異常或是提醒某個不常見的情況,例如:查詢前十篇熱門文章的推數,但發現找不到資料,因此自動補0。或是發現某個請求已經重複執行,因此不予再次執行直接返回。通常是在錯誤處理(error handling)的執行路徑上。
  • info: 在正常的執行路徑上,需要印出的關鍵資訊,例如:服務啟動、服務關閉。
  • debug: 在正常的執行路徑上,需要印出的輔助資訊。通常是執行請求或回圈內的上下文,特徵是在執行期間會重複出現這些log,在程式正常運作的情況下又不需要看。
  • trace: 埋在各個if-else程式段和for-loop內,用來詳細觀察程式執行流程,以及在關鍵點監測變數數值。這些log通常被用來檢查執行到某處時的變數值、malloc/free指標、埋在while或for開始處來檢查迴圈是否卡住。

使用的方法則是:

  1. 編譯Release版時移除trace的log,避免影響效能
  2. 建立三種log處理後端,分別是stdout/err, file, logging system。其中logging system是用來自動化監控用的,可以使用如elk或efk等三件套。
  3. 只把error(和以上)的log直接輸出到警告或工單系統,因此要謹慎決定是否使用error level。

metrics

metrics分成了多個層面,從維運的角度來看可以在這塊蒐集很多資訊,而開發者則需要聚焦在如何利用metrics暴露程式內的執行狀況。通常來說不同層面的metrics會有連動性,舉例來說CPU使用率非常高可能代表服務尖峰,或是程式出現異常狀況,所以需要同時觀察多個metrics來判斷原因。

開發者可以從這幾個角度來思考應該提供的metrics:

  1. 輸入
  2. 輸出
  3. queue長度
  4. 耗時
  5. 活動量

輸入/輸出也可以代換成請求/回應,藉由紀錄正常和異常的數量並判斷比例來初步評估一個服務是否處於正常運行的狀態。舉例來說像是HTTP狀態為5xx的比例如果過高,很有可能是服務遭遇某種嚴重的異常。

耗時和queue長度是另一組相關的數據。由於我們可能利用message queue來控制請求的併發數量,或是利用job queue來將工作異步化處理,如果消費者端發生問題導致來不及消化,症狀就會顯現在queue長度不斷增加。而耗時便是判斷在消費者端是否處理請求時發生異常,比如網路問題導致無法讀寫,或是上游api回應太慢。這個queue可以是分散式系統內的mq,也可以是程式內部交換資料的queue。如果開發者沒有使用類似job queue的方式,而是使用per job per thread的方式,也可以監控thread數量。

活動量是最後一類數據,比如連線數、訂閱數、目前執行的工作數量…。這些在開發環境中可能不太會製造問題,但在線上環境中有可能因為大量的連線或工作導致denied of service。

文章訊息

Search

    Table of Contents