如何使用 Prometheus 儀表化應用
我們已經知道了如何將應用的監控指標如何接入 Prometheus,但是如何在自己的應用程序中暴露監控指標呢?我們可以通過直接在應用中集成 metrics 指標數據,也可以單獨開發一個對應的 exporter 來暴露指標,或者根據需求編寫腳本推送到 pushgateway 網關生成監控指標,不過最好的方式是直接在應用程序中集成 Prometheus 監控指標數據。
接下來我們將來了解如何使用 Prometheus 客戶端庫來暴露監控指標,使用一個 Prometheus Go 客戶端庫來儀表化一個 Go 應用程序,直接在代碼中添加相關指標以獲取對應用程序的監控能力。
抓取指標
我們已經很清楚 Prometheus 是如何抓取監控指標的了,Prometheus 通過一個 HTTP 請求抓取監控目標,默認請求的端點名是 /metrics。
監控目標通過發送每個被跟蹤的時間序列單個樣本,以及樣本的指標名稱、標簽集合和樣本值來響應每個指標的當前狀態。抓取到數據后 Prometheus 會存儲每個樣本,并為其添加一個服務器端的時間戳,從而從單個抓取構建成一組時間序列。
此外我們再回顧下獲取的監控指標格式:
- # HELP http_requests_total The total number of HTTP requests.
- # TYPE http_requests_total counter
- http_requests_total{method="post",code="200"} 1027
- http_requests_total{method="post",code="400"} 3
- # HELP process_open_fds Number of open file descriptors.
- # TYPE process_open_fds gauge
- process_open_fds 15
- # HELP http_request_duration_seconds A histogram of the request duration.
- # TYPE http_request_duration_seconds histogram
- http_request_duration_seconds_bucket{le="0.05"} 24054
- http_request_duration_seconds_bucket{le="0.1"} 33444
- http_request_duration_seconds_bucket{le="0.2"} 100392
- http_request_duration_seconds_bucket{le="0.5"} 129389
- http_request_duration_seconds_bucket{le="1"} 133988
- http_request_duration_seconds_bucket{le="+Inf"} 144320
- http_request_duration_seconds_sum 53423
- http_request_duration_seconds_count 144320
- # HELP rpc_duration_seconds A summary of RPC durations in seconds.
- # TYPE rpc_duration_seconds summary
- rpc_duration_seconds{quantile="0.01"} 3.102
- rpc_duration_seconds{quantile="0.05"} 3.272
- rpc_duration_seconds{quantile="0.5"} 4.773
- rpc_duration_seconds{quantile="0.9"} 9.001
- rpc_duration_seconds{quantile="0.99"} 76.656
- rpc_duration_seconds_sum 5.7560473e+04
- rpc_duration_seconds_count 2693
抓取目標只會暴露當前訪問的值,而不會暴露它所跟蹤數據所有的歷史指標,指標中的每一行(注釋除外)就是一個時間序列的樣本,每個序列在同一個抓取中只能出現一次,所以,儀表化應用只需要在內存中跟蹤其指標的當前狀態即可,不需要跟蹤或緩存任何歷史指標狀態。
客戶端庫
Prometheus 官方已經提供了一些語言的客戶端庫,包括 Go、Java、Python、Ruby,還有一些非官方的第三方客戶端庫,可以用來幫助我們在應用中集成 Prometheus 指標服務。使用這些庫我們可以創建和跟蹤不同類型的指標,反映服務當前的狀態,這些庫都允許我們創建和更新單獨的指標對象,將它們注冊到一個指標注冊中心,然后通過 HTTP 暴露該指標注冊中心,也就是我們常用的 metrics 接口。
Prometheus 的儀表化客戶端庫中包含不同的指標類型:counters、gauges、histograms 以及 summaries,和 Prometheus 中的指標類型對應,具體要使用哪種類型的指標取決于我們的實際情況。
根據不同的指標類型,在構建指標對象的時候需要提供不同的選項,比如在創建直方圖的時候需要指定存儲桶 bucket,而創建計數器的時候下不需要其他額外參數的。此外構造的指標對象還為每種類型的指標暴露了不同的狀態更新方法,例如,計數器具有增加當前值的方法,但不會暴露將計數器設置為任意值的方法,但是儀表盤是允許我們設置當前值的。
另外 Prometheus 的客戶端庫頁面(https://prometheus.io/docs/instrumenting/clientlibs/)上列出的所有官方庫的實現都考慮到了效率和并發安全問題:
- 效率:對指標對象的狀態更新進行了優化
- 并發安全:指標對象的所有狀態更新以及從指標狀態讀取都是并發安全的,這意味著我們可以從多個線程(或 Go 中的 goroutines)更新指標值,而無需考慮鎖的問題,你的應用程序還能夠同時安全地處理多個指標抓取。
所以我們是可以放心(當然也是推薦)使用官方提供的客戶端庫來儀表化我們的應用程序的。
跟蹤指標當對一個系統或者服務進行儀表化的時候,盡量提供一些有意義的測量指標,業界有幾個比較著名的指導方針,可以幫助我們來理解在一個系統中應該添加哪些指標。
1.Google 的四大黃金指標
有 4 個來自 Google SRE 手冊的黃金指標,這 4 個指標主要針對應用程序或用戶部分。
- 延遲(Latency):服務請求所需耗時,例如 HTTP 請求平均延遲。需要區分成功請求和失敗請求,因為失敗請求可能會以非常低的延遲返回錯誤結果。
- 流量(Traffic):衡量服務容量需求(針對系統而言),例如每秒處理的 HTTP 請求數或者數據庫系統的事務數量。
- 錯誤(Errors):請求失敗的速率,用于衡量錯誤發生的情況,例如 HTTP500 錯誤數等顯式失敗,返回錯誤內容或無效內容等隱式失敗,以及由策略原因導致的失敗(比如強制要求響應時間超過 30ms 的請求為錯誤)。
- 飽和度(Saturation):衡量資源的使用情況,例如內存、CPU、I/O、磁盤使用量(即將飽和的部分,比如正在快速填充的磁盤)。
2.資源指標的 USE 方法
USE 是 Utilization(使用率)、Saturation(飽和度)、Error(錯誤)的首字母組合,是 Netflix 的內核和性能工程師 Brendan Gregg 提出的,主要用于分析系統性能問題,可以指導用戶快速識別資源瓶頸及錯誤,主要可以考慮添加以下指標。
- 使用率(Utilization):關注系統資源的使用情況,這里的資源主要包括但不限于 CPU、內存、網絡、磁盤等,100%使用率通常是系統性能瓶頸的標志。
- 飽和度(Saturation):例如調度器運行隊列長度,這里主要是針對資源的飽和度(注意,不同于四大黃金指標)。任何資源在某種程度上的飽和都可能導致系統性能的下降。
- 錯誤(Errors):發生了多少(以及什么類型的)錯誤。例如,網卡在數據包傳輸過程中檢測到以太網絡沖突了 10 次。
3.請求服務系統的 RED 方法
RED 方法是 Weave Cloud 基于 Google 的 4 個黃金指標再結合 Prometheus 及 Kubernetes 容器實踐得出的方法論,特別適用于對云原生應用以及微服務架構應用進行監控和度量。在四大黃金指標的原則下,RED 方法可以有效地幫助用戶衡量云原生以及微服務應用下的用戶體驗問題。RED 方法主要關注以下 3 種關鍵指標。
- Request Counters:請求計數器。
- Errors Counters:錯誤計數器。
- Request Duration:每個請求所花費的時間(histograms 或 summaries)。
一般來說,上述三大監控理論的最佳實踐是:在遵循 Google 四大黃金指標的前提下,對于在線系統,結合 RED 方法和緩存命中率方式進行監測;對于離線系統或者主機監控,以 USE 方法為主進行監測;對于批處理系統,可以采用類似 Pushgateway 的形式進行監控。
當然這些指南并不能完全覆蓋我們的實際監控需求,但是對于我們在應用中添加哪些指標提供了一個很好的指導作用。Prometheus 官方文檔中關于儀表化的最佳實踐(https://prometheus.io/docs/practices/instrumentation/)提供了更多關于不同類型系統監控的建議。
指標命名
一個時間序列的指標名稱描述了被監測系統的某些狀態,比如在如下所示的時間序列中:
- http_requests_total{job="nginx",instance="localhost:8080",method="POST"}
指標名稱就是標簽前面的 http_requests_total,該指標名稱本身字面意思就可以幫助我們理解該指標的含義了,雖然 Prometheus 本身并不會以語義方式解釋指標名稱。為了幫助標準化指標命名,Prometheus 官方文檔上列出了建議遵循的指標命名最佳實踐(https://prometheus.io/docs/practices/naming/#metric-names)。
- 必須符合數據模型中的有效字符
- 必須有一個與指標所屬領域相關的應用前綴,這個前綴有時被客戶端庫成為命名空間,對于特定與某個應用的指標,前綴通常是應用名稱本身,當然有時候指標也是比較通用的,比如 prometheus_notifications_total 是特定于 Prometheus 應用的指標,http_request_duration_seconds 用于所有的 HTTP 請求
- 必須有單一的單位,不要把秒和毫秒或字節這些混在一起
- 應該使用基本的單位,比如秒、字節、米等
- 應該有一個描述單位的后綴,采用復數形式,此外累積計數除了單位之外,還應該有一個總數 total 作為后綴,比如 http_reuqest_duration_seconds、node_memory_usage_bytes、http_requests_total (沒有單位的累計計數)、process_cpu_seconds_total (帶單位的累計計數)、foobar_build_info (用于提供關于運行中的應用元信息的指標)
請注意,直方圖和摘要還生成帶有后綴 _sum、_count 和 _bucket(單個直方圖桶的計數器)的計數器指標,但這些是根據基本指標名稱自動生成的直方圖,因此我們不需要手動指定這些后綴。
一個給定指標的所有維度上的 sum() 或 avg() 應該是有意義的(盡管不一定有用),如果沒有意義,請將數據拆分為多個指標。例如,將各種隊列的容量放在一個指標中是可行的,而將一個隊列的容量與隊列中的當前元素數混合在一起則是不規范的。
標簽
我們知道 Label 標簽是 Prometheus 中非常重要的一個元素,在我們儀表化應用的時候為指標指定合適的標簽也是非常重要的。我們知道每組唯一的標簽(包括指標名稱)都會標識并自動創建一個唯一的時間序列,Prometheus 會在查詢期間跟蹤、存儲和處理該時間序列,時間序列的數量也是 Prometheus 主要的性能瓶頸之一,對于稍好性能的服務器來說,通常可以很好的處理幾百萬的時間序列,當然最好不要太大,所以在決定將哪些標簽維度添加到指標中的時候,需要考慮到這一點。
Prometheus 總的時間序列成本需要通過指標上的不同標簽維度相乘得到,比如我們按照 status code 和 method 來拆分 HTTP 請求計數,則序列總數將是不同的 status code 和不同的 method 數量的乘積得到這兩個維度的所有有效組合,然后還需要將該基數乘以相同類型的受監控目標的數量,以得出 Prometheus 服務器的總體時間序列成本,所以對于標簽維度的控制是非常重要的,不能太少,也不能太多。
為避免時間序列數量激增,請保持你的每個標簽的可能值的數量有一定的限制。尤其要避免以下示例:
- 在一個標簽值中存儲公共 IP 地址或電子郵件地址
- 在一個標簽值中存儲完整的 HTTP 路徑,如果這些路徑包含 ID 或其他無限制的信息
- 或類似的模式
這將迅速產生一個不斷增加的時間系列,在短時間內使 Prometheus 服務器過載,所以我們要避免用這種方式的標簽值。接下來我們將學習使用 Prometheus 的 Go 客戶端庫(https://github.com/prometheus/client_golang)來為一個 Go 應用程序添加和暴露監控指標。