文章首發於【陳樹義】公眾號,點擊跳轉到原文:https://mp.weixin.qq.com/s/wnudWqfafzKUoDk4ke5Npg
我們在 樹義帶你學 Prometheus(三):Grafana 圖表配置快速入門 - 陳樹義 - 博客園 中提到可以針對業務指標做自定義監控,其中有一個設置屬性為 Metrics,即:

這個 Metrics 屬性的值遵守了 PromQL 規則。我們只要學會了 PromQL 表達式,就知道了怎么設置這個屬性了。
什么是 PromQL?
PromQL(Prometheus Query Language)是 Prometheus 內置的數據查詢語言,它能實現對事件序列數據的查詢、聚合、邏輯運算等。它並且被廣泛應用在 Prometheus 的日常應用當中,包括對數據查詢、可視化、告警處理當中。
簡單地說,PromQL 廣泛存在於以 Prometheus 為核心的監控體系中。所以需要用到數據篩選的地方,就會用到 PromQL。例如:監控指標的設置、報警指標的設置等等。
PromQL 基礎用法
當Prometheus通過Exporter采集到相應的監控指標樣本數據后,我們就可以通過PromQL對監控樣本數據進行查詢。
當我們直接使用監控指標名稱查詢時,可以查詢該指標下的所有時間序列。我們這里啟動 Prometheus 服務器,並打開 http://localhost:9090/graph 地址。在查詢框中,我們輸入:prometheus_http_requests_total 並點擊執行。

可以看到我們查詢出了所有指標名稱為 prometheus_http_requests_total 的數據。
PromQL 支持戶根據時間序列的標簽匹配模式來對時間序列進行過濾,目前主要支持兩種匹配模式:完全匹配和正則匹配。
完全匹配
文章首發於【陳樹義】公眾號,點擊跳轉到原文:https://mp.weixin.qq.com/s/wnudWqfafzKUoDk4ke5Npg
PromQL 支持使用 = 和 != 兩種完全匹配模式。
- 等於。通過使用
label=value可以選擇那些標簽滿足表達式定義的時間序列。 - 不等於。通過使用
label!=value則可以根據標簽匹配排除時間序列。
例如我們上面查詢出了所有指標名稱為 prometheus_http_requests_total 的數據。這時候我們希望只查看錯誤的請求,即過濾掉所有 code 標簽不是 200 的數據。那么我們的 PromQL 表達式可以修改為:prometheus_http_requests_total{code!="200"}。

從上圖可以看到,查詢出的結果已經過濾掉了所有 code 不為 200 的數據。
正則匹配
PromQL 還可以使用正則表達式作為匹配條件,並且可以使用多個匹配條件。
- 正向匹配。使用
label=~regx表示選擇那些標簽符合正則表達式定義的時間序列。 - 反向匹配。使用
label!~regx進行排除。
例如我想查詢指標 prometheus_http_requests_total 中,所有 handler 標簽以 /api/v1 開頭的記錄,那么我的表達式為:prometheus_http_requests_total{handler=~"/api/v1/.*"}。

從上面的查詢結果可以看出,查詢的結果已經只保留了handler 標簽以 /api/v1 開頭的數據。
范圍查詢
我們上面直接通過類似 prometheus_http_requests_total 表達式查詢時間序列時,同一個指標同一標簽只會返回一條數據。這樣的表達式我們稱之為瞬間向量表達式,而返回的結果稱之為瞬間向量。
而如果我們想查詢一段時間范圍內的樣本數據,那么我們就需要用到區間向量表達式,其查詢出來的結果稱之為區間向量。時間范圍通過時間范圍選擇器 [] 進行定義。例如,通過以下表達式可以選擇最近5分鍾內的所有樣本數據:
prometheus_http_requests_total{}[5m]

通過查詢結果可以看到,此時我們查詢出了所有的樣本數據,而不再是一個樣本數據的統計值。
除了使用m表示分鍾以外,PromQL的時間范圍選擇器支持其它時間單位:
- s - 秒
- m - 分鍾
- h - 小時
- d - 天
- w - 周
- y - 年
時間位移操作
在瞬時向量表達式或者區間向量表達式中,都是以當前時間為基准:
# 瞬時向量表達式,選擇當前最新的數據
prometheus_http_requests_total{}
# 區間向量表達式,選擇以當前時間為基准,5分鍾內的數據
prometheus_http_requests_total{}[5m]
文章首發於【陳樹義】公眾號,點擊跳轉到原文:https://mp.weixin.qq.com/s/wnudWqfafzKUoDk4ke5Npg
如果我們想查詢 5 分鍾前的瞬時樣本數據,或昨天一天的區間內的樣本數據呢? 這個時候我們就可以使用位移操作,位移操作的關鍵字為 offset。
# 查詢 5 分鍾前的最新數據
http_request_total{} offset 5m
# 往前移動 1 天,查詢 1 天前的數據
# 例如現在是 2020-10-07 00:00:00
# 那么這個表達式查詢的數據是:2020-10-05 至 2020-10-06 的數據
http_request_total{}[1d] offset 1d
聚合操作
一般情況下,我們通過 PromQL 查詢到的數據都是很多的。PromQL 提供的聚合操作可以用來對這些時間序列進行處理,形成一條新的時間序列。
以我們的 prometheus_http_requests_total 指標為例,不加任何條件我們查詢到的數據為:

從上圖查詢結果可以知道,一共有 8 條數據,這 8 條數據的 value 總和為 307。那么我們使用下面兩個聚合操作表達式來查詢,看看結果對不對。
第一個表達式,計算一共有幾條數據:count(prometheus_http_requests_total)。

第二個表達式,計算所有數據的 value 總和:sum(prometheus_http_requests_total)。

可以看到 count 的數值是一致的,都是 8。但是 sum 的數值有誤差,這是因為我們兩次查詢的時間間隔內,某些記錄的數值發生了變化。
文章首發於【陳樹義】公眾號,點擊跳轉到原文:https://mp.weixin.qq.com/s/wnudWqfafzKUoDk4ke5Npg
標量
在 PromQL 中,標量是一個浮點型的數字值,沒有時序。例如:10。
需要注意的是,當使用表達式count(http_requests_total),返回的數據類型,依然是瞬時向量。用戶可以通過內置函數scalar()將單個瞬時向量轉換為標量。

如上圖所示,我們將 sum 操作的值用 scalar 轉換了一下,最終的結果就是一個標量了。
字符串
在 PromQL 中,字符串是一個簡單的字符串值。直接使用字符串作為 PromQL 表達式,則會直接返回字符串。

上圖中我使用字符串 "this is a string" 直接作為 PromQL 查詢表達式,結果返回的是一個字符串。
PromQL 操作符
PromQL 還支持豐富的操作符,用戶可以使用這些操作符對進一步的對事件序列進行二次加工。這些操作符包括:數學運算符,邏輯運算符,布爾運算符等等。
數學運算符
數學運算符比較簡單,就是簡單的加減乘除等。
例如我們通過 prometheus_http_response_size_bytes_sum 可以查詢到 Prometheus 這個應用的 HTTP 響應字節總和。但是這個單位是字節,我們希望用 MB 顯示。那么我們可以這么設置:prometheus_http_response_size_bytes_sum/8/1024

最終顯示的數據就是以 MB 作為單位的數值。
PromQL支持的所有數學運算符如下所示:
+ (加法)- (減法)* (乘法)- / (除法)
- % (求余)
- ^ (冪運算)
布爾運算符
布爾運算符支持用戶根據時間序列中樣本的值,對時間序列進行過濾。例如我們可以通過 prometheus_http_requests_total 查詢出每個接口的請求次數,但是如果我們想篩選出請求次數超過 20 次的接口呢?
此時我們可以用下面的 PromQL 表達式:
prometheus_http_requests_total > 20
可以看到我們將所有 value 值超過 20 的數據都篩選了出來,從其指標名稱可以看出對應的接口名。

從上面的圖中我們可以看到,value 的值還是具體的數值。但如果我們希望對符合條件的數據,value 變為 1。不符合條件的數據,value 變為 0。那么我們可以使用bool修飾符。
我們使用下面的 PromQL 表達式:
prometheus_http_requests_total > bool 20
從下面的執行結果可以看到,這時候並不過濾掉數據,而是將 value 的值變成了 1 或 0。

目前,Prometheus支持以下布爾運算符如下:
* == (相等)!= (不相等)> (大於)< (小於)>= (大於等於)<= (小於等於)
集合運算符
通過集合運算,可以在兩個瞬時向量與瞬時向量之間進行相應的集合操作。目前,Prometheus支持以下集合運算符:
- and 與操作
- or 或操作
- unless 排除操作
and 與操作
vector1 and vector2 進行一個與操作,會產生一個新的集合。該集合中的元素同時在 vector1 和 vector2 中都存在。
例如我們有 vector1 為 A B C,vector2 為 B C D,那么 vector1 and vector2 的結果為:B C。
or 或操作
文章首發於【陳樹義】公眾號,點擊跳轉到原文:https://mp.weixin.qq.com/s/wnudWqfafzKUoDk4ke5Npg
vector1 and vector2 進行一個或操作,會產生一個新的集合。該集合中包含 vector1 和 vector2 中的所有元素。
例如我們有 vector1 為 A B C,vector2 為 B C D,那么 vector1 or vector2 的結果為:A B C D。
unless 排除操作
vector1 and vector2 進行一個或操作,會產生一個新的集合。該集合首先取 vector1 集合的所有元素,然后排除掉所有在 vector2 中存在的元素。
例如我們有 vector1 為 A B C,vector2 為 B C D,那么 vector1 unless vector2 的結果為:A。
操作符優先級
在PromQL操作符中優先級由高到低依次為:
^*, /, %+, -==, !=, <=, <, >=, >and, unlessor
PromQL 聚合操作
Prometheus 還提供了聚合操作符,這些操作符作用於瞬時向量。可以將瞬時表達式返回的樣本數據進行聚合,形成一個新的時間序列。目前支持的聚合函數有:
- sum (求和)
- min (最小值)
- max (最大值)
- avg (平均值)
- stddev (標准差)
- stdvar (標准方差)
- count (計數)
- count_values (對value進行計數)
- bottomk (后n條時序)
- topk (前n條時序)
- quantile (分位數)
sum 求和
用於對記錄的 value 值進行求和。
例如:sum(prometheus_http_requests_total) 表示統計所有 HTTP 請求的次數。

min 最小值
返回所有記錄的最小值。
prometheus_http_requests_total 指標所有數據如下圖所示:

當我們執行如下 PromQL 時,會篩選出最小的記錄值。
min(prometheus_http_requests_total)

max 最大值
返回所有記錄的最大值。
當我們執行如下 PromQL 時,會篩選出最大的記錄值。
max(prometheus_http_requests_total)

avg 平均值
avg 函數返回所有記錄的平均值。
當我們執行如下 PromQL 時,會篩選出最大的記錄值。
avg(prometheus_http_requests_total)

stddev 標准差
標准差(Standard Deviation)常用來描述數據的波動大小。例如我們統計籃球隊員身高:

兩支隊伍平均身高都是 180,看起來似乎差不多。但如果畫圖的話,得到結果如下:

很顯然,藍色隊隊員身高更加整齊一些,橙色隊身高顯得參差不齊。為了反映一組數據,偏離平均值的程度,就有了「標准差 」這個概念。
如果數據量很大,比如幾萬人的身高,我們不容易從折線圖看出來,可以直接用公式計算。上圖的數據標准差計算結果為:

很明顯,橙色隊的標准差比藍色隊標准差大很多。這說明橙色隊的身高波動更大。
當我們執行如下 PromQL 時,會計算出不同 HTTP 請求的數量波動情況。
stddev(prometheus_http_requests_total)

從計算結果可以看到,標准差達到了 1100 多,這說明其數據波動非常大。
count 計數
count 函數返回所有記錄的計數。
例如:count(prometheus_http_requests_total) 表示統計所有 HTTP 請求的次數。

bottomk 后幾條
bottomk 用於對樣本值進行排序,返回當前樣本值后 N 位的時間序列。
例如獲取 HTTP 請求量后 5 位的請求,可以使用表達式:
bottomk(5, prometheus_http_requests_total)

topk 前幾條
topk 用於對樣本值進行排序,返回當前樣本值前 N 位的時間序列。
例如獲取 HTTP 請求量前 5 位的請求,可以使用表達式:
topk(5, prometheus_http_requests_total)

PromQL 內置函數
PromQL 提供了大量的內置函數,可以對時序數據進行豐富的處理。例如 irate() 函數可以幫助我們計算監控指標的增長率,不需要我們去手動計算。
rate 增長率
我們知道 counter 類型指標的特點是只增不減,在沒有發生重置的情況下,其樣本值是不斷增大的。為了能直觀地觀察期變化情況,需要計算樣本的增長率。
increase(v range-vector) 函數是 PromQL 中提供的眾多內置函數之一。其中參數 v 是一個區間向量,increase 函數獲取區間向量中的第一個后最后一個樣本並返回其增長量。因此,可以通過以下表達式Counter類型指標的增長率:
increase(node_cpu[2m]) / 120
這里通過node_cpu[2m]獲取時間序列最近兩分鍾的所有樣本,increase計算出最近兩分鍾的增長量,最后除以時間120秒得到node_cpu樣本在最近兩分鍾的平均增長率。並且這個值也近似於主機節點最近兩分鍾內的平均CPU使用率。
除了使用increase函數以外,PromQL中還直接內置了rate(v range-vector)函數,rate函數可以直接計算區間向量v在時間窗口內平均增長速率。因此,通過以下表達式可以得到與increase函數相同的結果:
rate(node_cpu[2m])
需要注意的是使用rate或者increase函數去計算樣本的平均增長速率,容易陷入「長尾問題」當中,其無法反應在時間窗口內樣本數據的突發變化。
例如,對於主機而言在 2 分鍾的時間窗口內,可能在某一個由於訪問量或者其它問題導致 CPU 占用 100% 的情況,但是通過計算在時間窗口內的平均增長率卻無法反應出該問題。
為了解決該問題,PromQL提供了另外一個靈敏度更高的函數 irate(v range-vector)。irate 同樣用於計算區間向量的計算率,但是其反應出的是瞬時增長率。irate 函數是通過區間向量中最后兩個樣本數據來計算區間向量的增長速率。
這種方式可以避免在時間窗口范圍內的「長尾問題」,並且體現出更好的靈敏度,通過 irate 函數繪制的圖標能夠更好的反應樣本數據的瞬時變化狀態。
irate(node_cpu[2m])
irate函數相比於rate函數提供了更高的靈敏度,不過當需要分析長期趨勢或者在告警規則中,irate的這種靈敏度反而容易造成干擾。因此在長期趨勢分析或者告警中更推薦使用rate函數。
predict_linear 增長預測
在一般情況下,系統管理員為了確保業務的持續可用運行,會針對服務器的資源設置相應的告警閾值。例如,當磁盤空間只剩512MB時向相關人員發送告警通知。 這種基於閾值的告警模式對於當資源用量是平滑增長的情況下是能夠有效的工作的。
但是如果資源不是平滑變化的呢? 比如有些某些業務增長,存儲空間的增長速率提升了高幾倍。這時,如果基於原有閾值去觸發告警,當系統管理員接收到告警以后可能還沒來得及去處理問題,系統就已經不可用了。
因此閾值通常來說不是固定的,需要定期進行調整才能保證該告警閾值能夠發揮去作用。 那么還有沒有更好的方法嗎?
PromQL 中內置的 predict_linear(v range-vector, t scalar) 函數可以幫助系統管理員更好的處理此類情況,predict_linear 函數可以預測時間序列v在t秒后的值。
它基於簡單線性回歸的方式,對時間窗口內的樣本數據進行統計,從而可以對時間序列的變化趨勢做出預測。例如,基於2小時的樣本數據,來預測主機可用磁盤空間的是否在4個小時候被占滿,可以使用如下表達式:
predict_linear(node_filesystem_free{job="node"}[2h], 4 * 3600) < 0
