理解監(jiān)測指標,并使用Python去監(jiān)測它們
通過學習這些關鍵的術語和概念來理解 Python 應用監(jiān)測。
當我***次看到術語“計數(shù)器”和“計量器”和使用顏色及標記著“平均數(shù)”和“大于 90%”的數(shù)字圖表時,我的反應之一是逃避。就像我看到它們一樣,我并不感興趣,因為我不理解它們是干什么的或如何去使用。因為我的工作不需要我去注意它們,它們被我完全無視。
這都是在兩年以前的事了。隨著我的職業(yè)發(fā)展,我希望去了解更多關于我們的網(wǎng)絡應用程序的知識,而那個時候就是我開始去學習監(jiān)測指標的時候。
我的理解監(jiān)測的學習之旅共有三個階段(到目前為止),它們是:
- 階段 1:什么?(王顧左右)
- 階段 2:沒有指標,我們真的是瞎撞。
- 階段 3:出現(xiàn)不合理的指標我們該如何做?
我現(xiàn)在處于階段 2,我將分享到目前為止我學到了些什么。我正在向階段 3 進發(fā),在本文結束的位置我提供了一些我正在使用的學習資源。
我們開始吧!
需要的軟件
在文章中討論時用到的 demo 都可以在 我的 GitHub 倉庫 中找到。你需要安裝 docker 和 docker-compose 才能使用它們。
為什么要監(jiān)測?
關于監(jiān)測的主要原因是:
- 理解 正常的 和 不正常的 系統(tǒng)和服務的特征
- 做容量規(guī)劃、彈性伸縮
- 有助于排錯
- 了解軟件/硬件改變的效果
- 測量響應中的系統(tǒng)行為變化
- 當系統(tǒng)出現(xiàn)意外行為時發(fā)出警報
指標和指標類型
從我們的用途來看,一個指標就是在一個給定時間點上的某些數(shù)量的 測量 值。博客文章的總點擊次數(shù)、參與討論的總人數(shù)、在緩存系統(tǒng)中數(shù)據(jù)沒有被找到的次數(shù)、你的網(wǎng)站上的已登錄用戶數(shù) —— 這些都是指標的例子。
它們總體上可以分為三類:
計數(shù)器
以你的個人博客為例。你發(fā)布一篇文章后,過一段時間后,你希望去了解有多少點擊量,這是一個只會增加的數(shù)字。這就是一個計數(shù)器指標。在你的博客文章的生命周期中,它的值從 0 開始增加。用圖表來表示,一個計數(shù)器看起來應該像下面的這樣:
一個計數(shù)器指標總是在增加的。
計量器
如果你想去跟蹤你的博客每天或每周的點擊量,而不是基于時間的總點擊量。這種指標被稱為一個計量器,它的值可上可下。用圖表來表示,一個計量器看起來應該像下面的樣子:
一個計量器指標可以增加或減少。
一個計量器的值在某些時間窗口內(nèi)通常有一個***值和<ruby最小值floor。
柱狀圖和計時器
柱狀圖(在 Prometheus 中這么叫它)或計時器(在 StatsD 中這么叫它)是一個跟蹤 已采樣的觀測結果 的指標。不像一個計數(shù)器類或計量器類指標,柱狀圖指標的值并不是顯示為上或下的樣式。我知道這可能并沒有太多的意義,并且可能和一個計量器圖看上去沒有什么不同。它們的不同之處在于,你期望使用柱狀圖數(shù)據(jù)來做什么,而不是與一個計量器圖做比較。因此,監(jiān)測系統(tǒng)需要知道那個指標是一個柱狀圖類型,它允許你去做哪些事情。
一個柱狀圖指標可以增加或減少。
Demo 1:計算和報告指標
Demo 1 是使用 Flask 框架寫的一個基本的 web 應用程序。它演示了我們?nèi)绾稳?計算 和 報告 指標。
在 src
目錄中有 app.py
和 src/helpers/middleware.py
應用程序,包含以下內(nèi)容:
from flask import request
import csv
import time
def start_timer():
request.start_time = time.time()
def stop_timer(response):
# convert this into milliseconds for statsd
resp_time = (time.time() - request.start_time)*1000
with open('metrics.csv', 'a', newline='') as f:
csvwriter = csv.writer(f)
csvwriter.writerow([str(int(time.time())), str(resp_time)])
return response
def setup_metrics(app):
app.before_request(start_timer)
app.after_request(stop_timer)
當在應用程序中調(diào)用 setup_metrics()
時,它配置在一個請求被處理之前調(diào)用 start_timer()
函數(shù),然后在該請求處理之后、響應發(fā)送之前調(diào)用 stop_timer()
函數(shù)。在上面的函數(shù)中,我們寫了時間戳并用它來計算處理請求所花費的時間。
當我們在 demo1
目錄中運行 docker-compose up
,它會啟動這個 web 應用程序,然后在一個客戶端容器中可以生成一些對 web 應用程序的請求。你將會看到創(chuàng)建了一個 src/metrics.csv
文件,它有兩個字段:timestamp
和 request_latency
。
通過查看這個文件,我們可以推斷出兩件事情:
- 生成了很多數(shù)據(jù)
- 沒有觀測到任何與指標相關的特征
沒有觀測到與指標相關的特征,我們就不能說這個指標與哪個 HTTP 端點有關聯(lián),或這個指標是由哪個應用程序的節(jié)點所生成的。因此,我們需要使用合適的元數(shù)據(jù)去限定每個觀測指標。
《Statistics 101》
(LCTT 譯注:這是一本統(tǒng)計學入門教材的名字)
假如我們回到高中數(shù)學,我們應該回憶起一些統(tǒng)計術語,雖然不太確定,但應該包括平均數(shù)、中位數(shù)、百分位和柱狀圖。我們來簡要地回顧一下它們,不用去管它們的用法,就像是在上高中一樣。
平均數(shù)
平均數(shù),即一系列數(shù)字的平均值,是將數(shù)字匯總然后除以列表的個數(shù)。3、2 和 10 的平均數(shù)是 (3+2+10)/3 = 5。
中位數(shù)
中位數(shù)是另一種類型的平均,但它的計算方式不同;它是列表從小到大排序(反之亦然)后取列表的中間數(shù)字。以我們上面的列表中(2、3、10),中位數(shù)是 3。計算并不是非常直觀,它取決于列表中數(shù)字的個數(shù)。
百分位
百分位是指那個百(千)分比數(shù)字低于我們給定的百分數(shù)的程度。在一些場景中,它是指這個測量值低于我們數(shù)據(jù)的百(千)分比數(shù)字的程度。比如,上面列表中 95% 是 9.29999。百分位的測量范圍是 0 到 100(不包括)。0% 是一組數(shù)字的最小分數(shù)。你可能會想到它的中位數(shù)是 50%,它的結果是 3。
一些監(jiān)測系統(tǒng)將百分位稱為 upper_X
,其中 X 就是百分位;upper 90
指的是在 90% 的位置的值。
分位數(shù)
“q-分位數(shù)”是將有 N 個數(shù)的集合等分為 qN
級。q
的取值范圍為 0 到 1(全部都包括)。當 q
取值為 0.5 時,值就是中位數(shù)。(分位數(shù))和百分位數(shù)的關系是,分位數(shù)值 q
等于 100
百分位值。
柱狀圖
<ruby柱狀圖histogram這個指標,我們前面學習過,它是監(jiān)測系統(tǒng)中一個實現(xiàn)細節(jié)。在統(tǒng)計學中,一個柱狀圖是一個將數(shù)據(jù)分組為 桶 的圖表。我們來考慮一個人為的不同示例:閱讀你的博客的人的年齡。如果你有一些這樣的數(shù)據(jù),并想將它進行大致的分組,繪制成的柱狀圖將看起來像下面的這樣:
Histogram graph
累積柱狀圖
一個累積柱狀圖也是一個柱狀圖,它的每個桶的數(shù)包含前一個桶的數(shù),因此命名為累積。將上面的數(shù)據(jù)集做成累積柱狀圖后,看起來應該是這樣的:
Cumulative histogram
我們?yōu)槭裁葱枰鼋y(tǒng)計?
在上面的 Demo 1 中,我們注意到在我們報告指標時,這里生成了許多數(shù)據(jù)。當我們將它們用于指標時我們需要做統(tǒng)計,因為它們實在是太多了。我們需要的是整體行為,我們沒法去處理單個值。我們預期展現(xiàn)出來的值的行為應該是代表我們觀察的系統(tǒng)的行為。
Demo 2:在指標上增加特征
在我們上面的的 Demo 1 應用程序中,當我們計算和報告一個請求的延遲時,它指向了一個由一些特征 唯一標識的特定請求。下面是其中一些:
- HTTP 端點
- HTTP 方法
- 運行它的主機/節(jié)點的標識符
如果我們將這些特征附加到要觀察的指標上,每個指標將有更多的內(nèi)容。我們來解釋一下 Demo 2 中添加到我們的指標上的特征。
在寫入指標時,src/helpers/middleware.py
文件將在 CSV 文件中寫入多個列:
node_ids = ['10.0.1.1', '10.1.3.4']
def start_timer():
request.start_time = time.time()
def stop_timer(response):
# convert this into milliseconds for statsd
resp_time = (time.time() - request.start_time)*1000
node_id = node_ids[random.choice(range(len(node_ids)))]
with open('metrics.csv', 'a', newline='') as f:
csvwriter = csv.writer(f)
csvwriter.writerow([
str(int(time.time())), 'webapp1', node_id,
request.endpoint, request.method, str(response.status_code),
str(resp_time)
])
return response
因為這只是一個演示,在報告指標時,我們將隨意的報告一些隨機 IP 作為節(jié)點的 ID。當我們在 demo2
目錄下運行 docker-compose up
時,我們的結果將是一個有多個列的 CSV 文件。
用 pandas 分析指標
我們將使用 pandas 去分析這個 CSV 文件。運行 docker-compose up
將打印出一個 URL,我們將使用它來打開一個 Jupyter 會話。一旦我們上傳 Analysis.ipynb notebook
到會話中,我們就可以將 CSV 文件讀入到一個 pandas 數(shù)據(jù)幀中:
import pandas as pd
metrics = pd.read_csv('/data/metrics.csv', index_col=0)
index_col
表明我們要指定時間戳作為索引。
因為每個特征我們都要在數(shù)據(jù)幀中添加一個列,因此我們可以基于這些列進行分組和聚合:
import numpy as np
metrics.groupby(['node_id', 'http_status']).latency.aggregate(np.percentile, 99.999)
更多內(nèi)容請參考 Jupyter notebook 在數(shù)據(jù)上的分析示例。
我應該監(jiān)測什么?
一個軟件系統(tǒng)有許多的變量,這些變量的值在它的生命周期中不停地發(fā)生變化。軟件是運行在某種操作系統(tǒng)上的,而操作系統(tǒng)同時也在不停地變化。在我看來,當某些東西出錯時,你所擁有的數(shù)據(jù)越多越好。
我建議去監(jiān)測的關鍵操作系統(tǒng)指標有:
- CPU 使用
- 系統(tǒng)內(nèi)存使用
- 文件描述符使用
- 磁盤使用
還需要監(jiān)測的其它關鍵指標根據(jù)你的軟件應用程序不同而不同。
網(wǎng)絡應用程序
如果你的軟件是一個監(jiān)聽客戶端請求和為它提供服務的網(wǎng)絡應用程序,需要測量的關鍵指標還有:
- 入站請求數(shù)(計數(shù)器)
- 未處理的錯誤(計數(shù)器)
- 請求延遲(柱狀圖/計時器)
- 排隊時間,如果在你的應用程序中有隊列(柱狀圖/計時器)
- 隊列大小,如果在你的應用程序中有隊列(計量器)
- 工作進程/線程用量(計量器)
如果你的網(wǎng)絡應用程序在一個客戶端請求的環(huán)境中向其它服務發(fā)送請求,那么它應該有一個指標去記錄它與那個服務之間的通訊行為。需要監(jiān)測的關鍵指標包括請求數(shù)、請求延遲、和響應狀態(tài)。
HTTP web 應用程序后端
HTTP 應用程序應該監(jiān)測上面所列出的全部指標。除此之外,還應該按 HTTP 狀態(tài)代碼分組監(jiān)測所有非 200 的 HTTP 狀態(tài)代碼的大致數(shù)據(jù)。如果你的 web 應用程序有用戶注冊和登錄功能,同時也應該為這個功能設置指標。
長時間運行的進程
長時間運行的進程如 Rabbit MQ 消費者或任務隊列的工作進程,雖然它們不是網(wǎng)絡服務,它們以選取一個任務并處理它的工作模型來運行。因此,我們應該監(jiān)測請求的進程數(shù)和這些進程的請求延遲。
不管是什么類型的應用程序,都有指標與合適的元數(shù)據(jù)相關聯(lián)。
將監(jiān)測集成到一個 Python 應用程序中
將監(jiān)測集成到 Python 應用程序中需要涉及到兩個組件:
- 更新你的應用程序去計算和報告指標
- 配置一個監(jiān)測基礎設施來容納應用程序的指標,并允許去查詢它們
下面是記錄和報告指標的基本思路:
def work():
requests += 1
# report counter
start_time = time.time()
# < do the work >
# calculate and report latency
work_latency = time.time() - start_time
...
考慮到上面的模式,我們經(jīng)常使用修飾符、內(nèi)容管理器、中間件(對于網(wǎng)絡應用程序)所帶來的好處去計算和報告指標。在 Demo 1 和 Demo 2 中,我們在一個 Flask 應用程序中使用修飾符。
指標報告時的拉取和推送模型
大體來說,在一個 Python 應用程序中報告指標有兩種模式。在 拉取 模型中,監(jiān)測系統(tǒng)在一個預定義的 HTTP 端點上“刮取”應用程序。在推送 模型中,應用程序發(fā)送數(shù)據(jù)到監(jiān)測系統(tǒng)。
Pull and push models
工作在 拉取 模型中的監(jiān)測系統(tǒng)的一個例子是 Prometheus。而 StatsD 是 推送 模型的一個例子。
集成 StatsD
將 StatsD 集成到一個 Python 應用程序中,我們將使用 StatsD Python 客戶端,然后更新我們的指標報告部分的代碼,調(diào)用合適的庫去推送數(shù)據(jù)到 StatsD 中。
首先,我們需要去創(chuàng)建一個客戶端實例:
statsd = statsd.StatsClient(host='statsd', port=8125, prefix='webapp1')
prefix
關鍵字參數(shù)將為通過這個客戶端報告的所有指標添加一個指定的前綴。
一旦我們有了客戶端,我們可以使用如下的代碼為一個計時器報告值:
statsd.timing(key, resp_time)
增加計數(shù)器:
statsd.incr(key)
將指標關聯(lián)到元數(shù)據(jù)上,一個鍵的定義為:metadata1.metadata2.metric
,其中每個 metadataX 是一個可以進行聚合和分組的字段。
這個演示應用程序 StatsD 是將 statsd 與 Python Flask 應用程序集成的一個完整示例。
集成 Prometheus
要使用 Prometheus 監(jiān)測系統(tǒng),我們使用 Promethius Python 客戶端。我們將首先去創(chuàng)建有關的指標類對象:
REQUEST_LATENCY = Histogram('request_latency_seconds', 'Request latency',
['app_name', 'endpoint']
)
在上面的語句中的第三個參數(shù)是與這個指標相關的標識符。這些標識符是由與單個指標值相關聯(lián)的元數(shù)據(jù)定義的。
去記錄一個特定的觀測指標:
REQUEST_LATENCY.labels('webapp', request.path).observe(resp_time)
下一步是在我們的應用程序中定義一個 Prometheus 能夠刮取的 HTTP 端點。這通常是一個被稱為 /metrics
的端點:
@app.route('/metrics')
def metrics():
return Response(prometheus_client.generate_latest(), mimetype=CONTENT_TYPE_LATEST)
這個演示應用程序 Prometheus 是將 prometheus 與 Python Flask 應用程序集成的一個完整示例。
哪個更好:StatsD 還是 Prometheus?
本能地想到的下一個問題便是:我應該使用 StatsD 還是 Prometheus?關于這個主題我寫了幾篇文章,你可能發(fā)現(xiàn)它們對你很有幫助:
- 使用 Prometheus 監(jiān)測多進程 Python 應用的方式
- 使用 Prometheus 監(jiān)測你的同步 Python 應用
- 使用 Prometheus 監(jiān)測你的異步 Python 應用
指標的使用方式
我們已經(jīng)學習了一些關于為什么要在我們的應用程序上配置監(jiān)測的原因,而現(xiàn)在我們來更深入地研究其中的兩個用法:報警和自動擴展。
使用指標進行報警
指標的一個關鍵用途是創(chuàng)建警報。例如,假如過去的五分鐘,你的 HTTP 500 的數(shù)量持續(xù)增加,你可能希望給相關的人發(fā)送一封電子郵件或頁面提示。對于配置警報做什么取決于我們的監(jiān)測設置。對于 Prometheus 我們可以使用 Alertmanager,而對于 StatsD,我們使用 Nagios。
使用指標進行自動擴展
在一個云基礎設施中,如果我們當前的基礎設施供應過量或供應不足,通過指標不僅可以讓我們知道,還可以幫我們實現(xiàn)一個自動伸縮的策略。例如,如果在過去的五分鐘里,在我們服務器上的工作進程使用率達到 90%,我們可以水平擴展。我們?nèi)绾稳U展取決于云基礎設施。AWS 的自動擴展,缺省情況下,擴展策略是基于系統(tǒng)的 CPU 使用率、網(wǎng)絡流量、以及其它因素。然而,讓基礎設施伸縮的應用程序指標,我們必須發(fā)布 自定義的 CloudWatch 指標。
在多服務架構中的應用程序監(jiān)測
當我們超越一個單應用程序架構時,比如當客戶端的請求在響應被發(fā)回之前,能夠觸發(fā)調(diào)用多個服務,就需要從我們的指標中獲取更多的信息。我們需要一個統(tǒng)一的延遲視圖指標,這樣我們就能夠知道響應這個請求時每個服務花費了多少時間。這可以用 分布式跟蹤 來實現(xiàn)。
你可以在我的博客文章 《在你的 Python 應用程序中通過 Zipkin 引入分布式跟蹤》 中看到在 Python 中進行分布式跟蹤的示例。
劃重點
總之,你需要記住以下幾點:
- 理解你的監(jiān)測系統(tǒng)中指標類型的含義
- 知道監(jiān)測系統(tǒng)需要的你的數(shù)據(jù)的測量單位
- 監(jiān)測你的應用程序中的大多數(shù)關鍵組件
- 監(jiān)測你的應用程序在它的大多數(shù)關鍵階段的行為
以上要點是假設你不去管理你的監(jiān)測系統(tǒng)。如果管理你的監(jiān)測系統(tǒng)是你的工作的一部分,那么你還要考慮更多的問題!
其它資源
以下是我在我的監(jiān)測學習過程中找到的一些非常有用的資源:
綜合的
StatsD/Graphite
Prometheus
- Prometheus 指標類型
- Prometheus 計量器如何工作?
- 為什么用 Prometheus 累積柱形圖?
- 在 Python 中監(jiān)測批量作業(yè)
- Prometheus:監(jiān)測 SoundCloud
避免犯錯(即第 3 階段的學習)
在我們學習監(jiān)測的基本知識時,時刻注意不要犯錯誤是很重要的。以下是我偶然發(fā)現(xiàn)的一些很有見解的資源:
- 如何不測量延遲
- Prometheus 柱形圖:悲傷的故事
- 為什么平均值很討厭,而百分位很棒
- 對延遲的認知錯誤
- 誰動了我的 99% 延遲?
- 日志、指標和圖形
- HdrHistogram:一個更好的延遲捕獲方式