<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          保姆級別的 PromQL 教程

          共 8594字,需瀏覽 18分鐘

           ·

          2021-10-02 21:16


          這篇文章介紹如何使用 PromQL 查詢 Prometheus 里面的數(shù)據(jù)。包括如何使用函數(shù),理解這些函數(shù),Metrics 的邏輯等等,因為看了很多教程試圖學習 PromQL,發(fā)現(xiàn)這些教程都直說有哪些函數(shù)、語法是什么,看完之后還是很難理解。比如 [1m] 是什么意思?為什么有的函數(shù)需要有的函數(shù)不需要?它對 Grafana 上面展示的數(shù)據(jù)有什么影響?rateirate 的區(qū)別是什么?sumrate 要先用哪個后用哪個?經(jīng)過照葫蘆畫瓢地寫了很多 PromQL 來設(shè)置監(jiān)控和告警規(guī)則,我漸漸對 PromQL 的邏輯有了一些理解。這篇文章從頭開始,通過介紹 PromQL 里面的邏輯,來理解這些函數(shù)的作用。本文不會一一回答上面這些問題,但是我的這些問題都是由于之前對 PromQL 里面的邏輯和概念不了解,相信讀完本文之后,這些問題的答案就顯得不言而喻了。

          本文不會深入講解 Prometheus 的數(shù)據(jù)存儲原理,Prometheus 對 metrics 的抓取原理等問題;也不會深入介紹 PromQL 中每一個 API 的實現(xiàn)。只會著重于介紹如何寫 PromQL 的原理,和它的設(shè)計邏輯。但是相信如果理解了本文這些概念,可以更透徹地理解和閱讀 Prometheus 官方的文檔。

          Metric 類型

          Prometheus 里面其實只有兩種數(shù)據(jù)類型。Gauge 和 Counter。

          Gauge

          Gauge 是比較符合直覺的。它就是表示一個當前的“狀態(tài)”,比如內(nèi)存當前是多少,CPU 當前的使用率是多少。

          Counter

          Counter 有一些不符合直覺。我想了很久才理解(可能我有點鉆牛角尖了)。Counter 是一個永遠只遞增的 Metric 類型。

          使用 Counter 計算得到的,每秒收到的 packet 數(shù)量

          典型的 Counter 舉例:服務器服務的請求數(shù),服務器收到了多少包(上圖)。這個數(shù)字是只增不減的,用 Counter 最合適了。因為每一個時間點的總請求數(shù)都會包含之前時間點的請求數(shù),所以可以理解成它是一個“有狀態(tài)的”(非官方說法,我這么說只是為了方便讀者理解)。使用 Counter 記錄每一個時間點的“總數(shù)”,然后除以時間,就可以得到 QPS,packets/s 等數(shù)據(jù)。

          為什么需要 Counter 呢?先來回顧一下 Gauge,你可以將 Gauge 理解為“無狀態(tài)的”,即類型是 Gauge 的 metric 不需要關(guān)心歷史的值,只需要記錄當前的值是多少就可以了。比如當前的內(nèi)存值,當前的 CPU 使用率。當然,如果你想要查詢歷史的值,依然是可以查到的。只不過對于每一個時間點的“內(nèi)存使用量”這個 Gauge,不包含歷史的數(shù)據(jù)。那么可否用 Gauge 來代替 Counter 呢?

          Prometheus 是一個抓取的模型:服務器暴露一個 HTTP 服務,Prometheus 來訪問這個 HTTP 接口來獲取 metrics 的數(shù)據(jù)。如果使用 Gauge 來表示上面的 pk/s 數(shù)據(jù)的話,只能使用這種方案:使用這個 Metric 記錄自從上次抓取過后收到的 Packet 總數(shù)(或者直接記錄 Packet/s ,原理是一樣的)。每次 Prometheus 來抓取數(shù)據(jù)之后,就將這個值重置為 0. 這樣的實現(xiàn)就類似 Gauge 了。

          Prometheus 的抓取模型,去訪問服務的 HTTP 來抓取 metrics

          這種實現(xiàn)的缺點有:

          1. 抓取數(shù)據(jù)本質(zhì)是 GET 操作,但是這個 GET 操作卻會修改數(shù)據(jù)(將 metric 重置為 0),所以會帶來很多隱患,比如一個服務每次只能由一個 Prometheus 來抓取,不能擴展;不能 cURL 這個 /metrics 來進行 debug,因為會影響真實的數(shù)據(jù),等等。
          2. 如果服務器發(fā)生了重啟,數(shù)據(jù)將會清零,會丟失數(shù)據(jù)(雖然 Counter 也沒有從本質(zhì)上解決這個問題)。

          Counter 因為是一個只遞增的值,所以它可以判斷數(shù)字下降的問題,比如現(xiàn)在請求的 Count 數(shù)是 1000,然后下次 Prometheus 來抓取發(fā)現(xiàn)變成了 20,那么 Prometheus 就知道,真實的數(shù)據(jù)不可能是 20,因為請求數(shù)是不可能下降的。所以它會將這個點認為是 1020。

          然后用 Counter 也可以解決多次讀的問題,服務器上的 /metrics,可以使用 cURL 和 grep 等工具實時查看,不會改變數(shù)據(jù)。Counter 有關(guān)的細節(jié)可以參考下 How does a Prometheus Counter work?[1]

          其實 Prometheus 里面還有兩種數(shù)據(jù)類型,一種是 Histogram,另一種是 Summary.

          但是這兩種類型本質(zhì)上都是 Counter。比如,如果你要統(tǒng)計一個服務處理請求的平均耗時,就可以用 Summary。在代碼中只用一種 Summary 類型[2],就可以暴露出收到的總請求數(shù),處理這些請求花費的總時間數(shù),兩個 Counter 類型的 metric。算是一個“語法糖”。

          Histogram 是由多個 Counter 組成的一組(bucket)metrics,比如你要統(tǒng)計 P99 的信息,使用 Histogram 可以暴露出 10 個 bucket 分別存放不同耗時區(qū)間的請求數(shù),使用 histogram_quantile 函數(shù)就可以方便地計算出 P99(《P99 是如何計算的?[3]》). 本質(zhì)上也是一個“糖”。假如 Prometheus 沒有 Histogram 和 Summary 這兩種 Metric 類型,也是完全可以的,只不過我們在使用上就需要多做很多事情,麻煩一些。

          講了這么說,希望讀者已經(jīng)明白 Counter 和 Gauge 了。因為我們接下來的查詢會一直跟這兩種 Metric 類型打交道。

          Selectors

          下面這張圖簡單地表示了 Metric 在 Prometheus 中的樣子,以給讀者一個概念。

          如果我們直接在 Grafana 中使用 node_network_receive_packets_total 來畫圖的話,就會得到 5 條線。

          Counter 的值很大,并且此圖基本上看不到變化趨勢。因為它們只增加,可以認為是這個服務器自存在以來收到的所有的包的數(shù)量。

          Metric 可以通過 label 來進行選擇,比如 node_network_receive_packets_total{device=”bond0″} 就會只查詢到 bond0 的數(shù)據(jù),繪制 bond0 這個 device 的曲線。也支持正則表達式,可以通過 node_network_receive_packets_total{device=~”en.*”} 繪制 en0 和 en2 的曲線。

          其實,metric name 也是一個 “l(fā)abel”, 所以 node_network_receive_packets_total{device="bond0"} 本質(zhì)上是 {__name__="node_network_receive_packets_total", device="bond0"} 。但是因為 metric name 基本上是必用的 label,所以我們一般用第一種寫法, 這樣看起來更易懂。

          PromQL 支持很復雜的 Selector,詳細的用法可以參考文檔[4]。指的一提的是,Prometheus 是圖靈完備 (Turing Complete)[5]的(Surprise!)。

          實際上,如果你使用下面的查詢語句,將會僅僅得到一個數(shù)字,而不是整個 metric 的歷史數(shù)據(jù)(node_network_receive_packets_total{device=~"en.*"} 得到的是下圖中黃色的部分。

          這個就是 Instant Vector:只查詢到 metric 的在某個時間點(默認是當前時間)的值。

          PromQL 語言的數(shù)據(jù)類型

          為了避免讀者混淆,這里說明一下 Metric Type 和 PromQL 查詢語言中的數(shù)據(jù)類型的區(qū)別。很簡單,在寫 PromQL 的時候,無論是 Counter 還是 Gauge,對于函數(shù)來說都是一串數(shù)字,他們數(shù)據(jù)結(jié)構(gòu)上沒有區(qū)別。我們說的 Instant Vector 還是 Range Vector, 指的是 PromQL 函數(shù)的入?yún)⒑头祷刂档念愋汀?/p>

          Instant Vector

          Instant 是立即的意思,Instant Vector 顧名思義,就是當前的值。假如查詢的時間點是 t,那么查詢會返回距離 t 時間點最近的一個值。

          常用的另一種數(shù)據(jù)類型是 Range Vector。

          Range Vector

          Range Vector 顧名思義,返回的是一個 range 的數(shù)據(jù)。

          Range 的表示方法是 [1m],表示 1 分鐘的數(shù)據(jù)。也可以使用 [1h] 表示 1 小時,[1d] 表示 1 天。支持的所有的 duration 表示方法可以參考文檔[6]

          假如我們對 Prometheus 的采集配置是每 10s 采集一次,那么 1 分鐘內(nèi)就會有采集 6 次,就會有 6 個數(shù)據(jù)點。我們使用 node_network_receive_packets_total{device=~“.*”}[1m] 查詢的話,就可以得到以下的數(shù)據(jù):兩個 metric,最后的 6 個數(shù)據(jù)點。

          Prometheus 大部分的函數(shù)要么接受的是 Instant Vector,要么接受的是 Range Vector。所以要看懂這些函數(shù)的文檔,就要理解這兩種類型。

          在詳細解釋之前,請讀者思考一個問題:在 Grafana 中畫出來一個 Metric 的圖標,需要查詢結(jié)果是一個 Instant Vector,還是 Range Vector 呢?

          答案是 Instant Vector (Surprise!)。

          為什么呢?要畫出一段時間的 Chart,不應該需要一個 Range 的數(shù)據(jù)嗎?為什么是 Instant Vector?

          答案是:Range Vector 基本上只是為了給函數(shù)用的,Grafana 繪圖只能接受 Instant Vector。Prometheus 的查詢 API 是以 HTTP 的形式提供的,Grafana 在渲染一個圖標的時候會向 Prometheus 去查詢數(shù)據(jù)。而這個查詢 API 主要有兩種:

          第一種是 /query:查詢一個時間點的數(shù)據(jù),返回一個數(shù)據(jù)值,通過 ?time=1627111334 可以查詢指定時間的數(shù)據(jù)。

          假如要繪制 1 個小時內(nèi)的 Chart 的話,Grafana 首先需要你在創(chuàng)建 Chart 的時候傳入一個 step 值,表示多久查一個數(shù)據(jù),這里假設(shè) step=1min 的話,我們對每分鐘需要查詢一次數(shù)據(jù)。那么 Grafana 會向 Prometheus 發(fā)送 60 次請求,查詢 60 個數(shù)據(jù)點,即 60 個 Instant Vector,然后繪制出來一張圖表。

          Grafana 的 step 設(shè)置

          當然,60 次請求太多了。所以就有了第二種 API query_range,接收的參數(shù)有 ?start=&end=&step=60。但是這個 API 本質(zhì)上,是一個語法糖,在 Prometheus 內(nèi)部還是對 60 個點進行了分別計算,然后返回。當然了,會有一些優(yōu)化。

          然后就有了下一個問題:為什么 Grafana 偏偏要繪制 Instant Vector,而不是 Range Vector 呢?

          Grafana 只接受 Instant Vector, 如果查詢的結(jié)果是 Range Vector, 會報錯

          因為這里的 Range Vector 并不是一個“繪制的時間”,而是函數(shù)計算所需要的時間區(qū)間。看下面的例子就容易理解了。

          來解釋一下這個查詢:

          rate(node_network_receive_packets_total{device=~”en.*”}[1m])

          查詢每秒收到的 packet 數(shù)量

          node_network_receive_packets_total 是一個 Counter,為了計算每秒的 packet 數(shù)量,我們要計算每秒的數(shù)量,就要用到 rate 函數(shù)。

          先來看一個時間點的計算,假如我們計算 t 時間點的每秒 packet 數(shù)量,rate 函數(shù)可以幫我們用這段時間([1m])的總 packet 數(shù)量,除以時間 [1m] ,就得到了一個“平均值”,以此作為曲線來繪制。

          以這種方法就得到了一個點的數(shù)據(jù)。

          然后我們對之前的每一個點,都以此法進行計算,就得到了一個 pk/s 的曲線(最長的那條是原始的數(shù)據(jù),黃色的表示 rate 對于每一個點的計算過程,藍色的框為最終的繪制的點)。

          所以這個 PromQL 查詢最終得到的數(shù)據(jù)點是:… 2.2, 1.96, 2.31, 2, 1.71 (即藍色的點)。

          這里有兩個選中的 metric,分別是 en0en2,所以 rate 會分別計算兩條曲線,就得到了上面的 Chart,有兩條線。

          rate, irate 和 increase

          很多人都會糾結(jié) iraterate 有什么區(qū)別。看到這里,其實就很好解釋了。

          以下來自官方的文檔:

          irate() irate(v range-vector) calculates the per-second instant rate of increase of the time series in the range vector. This is based on the last two data points.

          即,irate 是計算的最后兩個點之間的差值。可以用下圖來表示:

          irate 的計算方式

          自然,因為只用最后兩個點的差值來計算,會比 rate 平均值的方法得到的結(jié)果,變化更加劇烈,更能反映當時的情況。那既然是使用最后兩個點計算,這里又為什么需要 [1m] 呢?這個 [1m] 不是用來計算的,是用來限制找 t-2 個點的時間的,比如,如果中間丟了很多數(shù)據(jù),那么顯然這個點的計算會很不準確,irate 在計算的時候會最多向前在 [1m] 找點,如果超過 [1m] 沒有找到數(shù)據(jù)點,這個點的計算就放棄了。

          在現(xiàn)實中的例子,可以將上面查詢的 rate 改成 irate

          irate(node_network_receive_packets_total{device=~”en.*”}[1m])

          對比與之前的圖,可以看到變化更加劇烈了。

          那么,是不是我們總是使用 irate 比較好呢?也不是,比如 requests/s 這種,如果變化太劇烈,從面板上你只能看到一條劇烈抖動導致看不清數(shù)值的曲線,而具體值我們是不太關(guān)心的,我們可能更關(guān)心一天中的 QPS 變化情況;但是像是 CPU,network 這種資源的變化,使用 irate 更加有意義一些。

          還有一個函數(shù)叫做 increase,它的計算方式是 end - start,沒有除。計算的是每分鐘的增量。比較好理解,這里就不畫圖了。

          這三個函數(shù)接受的都是 Range Vector,返回的是 Instant Vector,比較常用。

          另外需要注意的是,increaserate 的 range 內(nèi)必須要有至少 4 個數(shù)據(jù)點。詳細的解釋可以見這里:What range should I use with rate()?[7]

          介紹了這兩種類型,那么其他的 Prometheus 函數(shù)[8]應該都可以看文檔理解了。Prometheus 的文檔中會將函數(shù)這樣標注:

          changes() For each input time series, changes(v range-vector) returns the number of times its value has changed within the provided time range as an instant vector.

          我們就知道,changes() 這個函數(shù)接受的是一個 range-vector, 所以要帶上類似于 [1m] 。不能傳入不帶類似 [1m] 的 metrics,類似于這樣的使用是不合法的:change(requests_count{server="server_a"},這樣就相當于傳入了一個 Instant Vector。

          看到這里,你應該已經(jīng)成為一只在 Prometheus 里面自由翱翔的鳥兒了。接下來可以抱著文檔[9]去寫查詢了,但是在這之前,讓我再介紹一點非常重要的誤區(qū)。

          使用函數(shù)的順序問題

          在計算 P99 的時候,我們會使用下面的查詢:

          histogram_quantile(0.9,
          ????sum?by?(le)
          ????(rate(http_request_duration_seconds_bucket[10m]))
          )

          首先,Histogram 是一個 Counter,所以我們要使用 rate 先處理,然后根據(jù) le 將 labels 使用 sum 合起來,最后使用 histogram_quantile 來計算。這三個函數(shù)的順序是不能調(diào)換的,必須是先 ratesum,最后 histogram_quantile

          為什么呢?這個問題可以分成兩步來看:

          rate 必須在 sum 之前。前面提到過 Prometheus 支持在 Counter 的數(shù)據(jù)有下降之后自動處理的,比如服務器重啟了,metric 重新從 0 開始。這個其實不是在存儲的時候做的,比如應用暴露的 metric 就是從 2033 變成 0 了,那么 Prometheus 就會忠實地存儲 0. 但是在計算 rate 的時候,就會識別出來這個下降。但是 sum 不會,所以如果先 sumrate,曲線就會出現(xiàn)非常大的波動。詳細見這里[10]

          histogram_quantile 必須在最后。在《P99 是如何計算的?[11]》這篇文章中介紹了 P99 的原理。也就是說 histogram_quantile 計算的結(jié)果是近似值,去聚合(無論是 sum 還是 max 還是 avg)這個值都是沒有意義的。

          引用鏈接

          [1]

          How does a Prometheus Counter work?: https://www.robustperception.io/how-does-a-prometheus-counter-work

          [2]

          在代碼中只用一種 Summary 類型: https://github.com/prometheus/client_python#summary

          [3]

          P99 是如何計算的?: https://www.kawabangga.com/posts/4284

          [4]

          參考文檔: https://prometheus.io/docs/prometheus/latest/querying/basics/

          [5]

          圖靈完備 (Turing Complete): https://www.robustperception.io/conways-life-in-prometheus

          [6]

          參考文檔: https://prometheus.io/docs/prometheus/latest/querying/basics/#time-durations

          [7]

          What range should I use with rate()?: https://www.robustperception.io/what-range-should-i-use-with-rate

          [8]

          Prometheus 函數(shù): https://prometheus.io/docs/prometheus/latest/querying/functions/

          [9]

          文檔: https://prometheus.io/docs/introduction/overview/

          [10]

          這里: https://www.robustperception.io/rate-then-sum-never-sum-then-rate

          [11]

          P99 是如何計算的?: https://www.kawabangga.com/posts/4284


          原文鏈接:https://www.kawabangga.com/posts/4408


          你可能還喜歡

          點擊下方圖片即可閱讀

          使用 Promethues 實現(xiàn)應用監(jiān)控的一些實踐

          云原生是一種信仰???


          關(guān)注公眾號

          后臺回復?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!



          點擊?"閱讀原文"?獲取更好的閱讀體驗!


          發(fā)現(xiàn)朋友圈變“安靜”了嗎?

          瀏覽 169
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩一级在线免费观看 | 免费aⅴ在线| 黄色在线免费在线免费 | 亚洲综合久 | 大香蕉电影院 |