一、Prometheus中的基本概念
Prometheus將所有數據存儲為時間序列,這里先來了解一下prometheus中的一些基本概念
指標名和標簽
每個時間序列都由指標名和一組鍵值對(也稱為標簽)唯一標識。
metric的格式如下:
<metric name>{<label name>=<label value>, ...}
例如:
http_requests_total{host="192.10.0.1", method="POST", handler="/messages"}
http_requests_total是指標名;
host、method、handler是三個標簽(label),也就是三個維度;
查詢語句可以基於這些標簽or維度進行過濾和聚合;
指標類型
Prometheus client庫提供四種核心度量標准類型。注意是客戶端。Prometheus服務端沒有區分類型,將所有數據展平為無類型時間序列。
1、 Counter:只增不減的累加指標
Counter就是一個計數器,表示一種累積型指標,該指標只能單調遞增或在重新啟動時重置為零,例如,您可以使用計數器來表示所服務的請求數,已完成的任務或錯誤。
2、 Gauge:可增可減的測量指標
Gauge是最簡單的度量類型,只有一個簡單的返回值,可增可減,也可以set為指定的值。所以Gauge通常用於反映當前狀態,比如當前溫度或當前內存使用情況;當然也可以用於“可增加可減少”的計數指標。
3、Histogram:自帶buckets區間用於統計分布的直方圖
Histogram主要用於在設定的分布范圍內(Buckets)記錄大小或者次數。
例如http請求響應時間:0-100ms、100-200ms、200-300ms、>300ms 的分布情況,Histogram會自動創建3個指標,分別為:
事件發送的總次數<basename>_count:比如當前一共發生了2次http請求
所有事件產生值的大小的總和<basename>_sum:比如發生的2次http請求總的響應時間為150ms
事件產生的值分布在bucket中的次數<basename>_bucket{le="上限"}:比如響應時間0-100ms的請求1次,100-200ms的請求1次,其他的0次
4、Summary:數據分布統計圖
Summary和Histogram類似,都可以統計事件發生的次數或者大小,以及其分布情況。
Summary和Histogram都提供了對於事件的計數_count以及值的匯總_sum,因此使用_count,和_sum時間序列可以計算出相同的內容。
同時Summary和Histogram都可以計算和統計樣本的分布情況,比如中位數,n分位數等等。不同在於Histogram可以通過histogram_quantile函數在服務器端計算分位數。 而Sumamry的分位數則是直接在客戶端進行定義。因此對於分位數的計算。 Summary在通過PromQL進行查詢時有更好的性能表現,而Histogram則會消耗更多的資源。相對的對於客戶端而言Histogram消耗的資源更少。
作業和實例
在Prometheus中,一個可以拉取數據的端點IP:Port叫做一個實例(instance),而具有多個相同類型實例的集合稱作一個作業(job)
- job: api-server - instance 1: 1.2.3.4:5670 - instance 2: 1.2.3.4:5671 - instance 3: 5.6.7.8:5670 - instance 4: 5.6.7.8:5671
當Prometheus拉取指標數據時,會自動生成一些標簽(label)用於區別抓取的來源:
job:配置的作業名;
instance:配置的實例名,若沒有實例名,則是抓取的IP:Port。
對於每一個實例(instance)的抓取,Prometheus會默認保存以下數據:
up{job="<job>", instance="<instance>"}:如果實例是健康的,即可達,值為1,否則為0;
scrape_duration_seconds{job="<job>", instance="<instance>"}:抓取耗時;
scrape_samples_post_metric_relabeling{job="<job>", instance="<instance>"}:指標重新標記后剩余的樣本數。
scrape_samples_scraped{job="<job>", instance="<instance>"}:實例暴露的樣本數
該up指標對於監控實例健康狀態很有用。
二、最簡單的Exporter
當你安裝好go的開發環境,並下載好Prometheus依賴包到vendor以后,就可以編譯個最簡單的Exporter,代碼如下:
1 package main 2 3 import ( 4 "log" 5 "net/http" 6 "github.com/prometheus/client_golang/prometheus/promhttp" 7 ) 8 9 func main() { 10 http.Handle("/metrics", promhttp.Handler()) 11 log.Fatal(http.ListenAndServe(":8080", nil)) 12 }
執行go build編譯運行,然后訪問http://127.0.0.1:8080/metrics就可以看到采集到的指標數據。
這段代碼僅僅通過http模塊指定了一個路徑/metrics,並將client_golang庫中的promhttp.Handler()作為處理函數傳遞進去后,就可以獲取指標數據了。這個最簡單的 Exporter 內部其實是使用了一個默認的收集器NewGoCollector采集當前Go運行時的相關信息,比如go堆棧使用、goroutine數據等等。
三、Demo Exporter的目錄結構
項目的目錄結構如下:
1 prometheus-exporter/ 2 |-- collector 3 `-- vendor 4 `-- github.com 5 |-- beorn7 6 |-- golang 7 |-- matttproud 8 `-- prometheus
vendor是項目依賴的外部包
collector實現一個采集器,用於采集指標數據
四、代碼實現
包括以下幾個主要的步驟。
1、定義指標
定義指標就是創建指標的描述符,通常把要采集的指標描述符放在一個結構體里:
// 指標結構體 type Metrics struct { metrics map[string]*prometheus.Desc mutex sync.Mutex } /** * 函數:newGlobalMetric * 功能:創建指標描述符 */ func newGlobalMetric(namespace string, metricName string, docString string, labels []string) *prometheus.Desc { return prometheus.NewDesc(namespace+"_"+metricName, docString, labels, nil) } /** * 工廠方法:NewMetrics * 功能:初始化指標信息,即Metrics結構體 */ func NewMetrics(namespace string) *Metrics { return &Metrics{ metrics: map[string]*prometheus.Desc{ "my_counter_metric": newGlobalMetric(namespace, "my_counter_metric", "The description of my_counter_metric", []string{"host"}), "my_gauge_metric": newGlobalMetric(namespace, "my_gauge_metric","The description of my_gauge_metric", []string{"host"}), }, } }
調用工廠方法即可創建一個結構體的實例
2、注冊指標
metrics := collector.NewMetrics(*metricsNamespace) // 創建指標結構體實例 registry := prometheus.NewRegistry() registry.MustRegister(metrics) // 注冊指標
3、數據采集
數據采集需要實現collector的兩個接口:
1 /** 2 * 接口:Describe 3 * 功能:傳遞結構體中的指標描述符到channel 4 */ 5 func (c *Metrics) Describe(ch chan<- *prometheus.Desc) { 6 for _, m := range c.metrics { 7 ch <- m 8 } 9 } 10 11 /** 12 * 接口:Collect 13 * 功能:抓取最新的數據,傳遞給channel 14 */ 15 func (c *Metrics) Collect(ch chan<- prometheus.Metric) { 16 c.mutex.Lock() // 加鎖 17 defer c.mutex.Unlock() 18 19 mockCounterMetricData, mockGaugeMetricData := c.GenerateMockData() 20 for host, currentValue := range mockCounterMetricData { 21 ch <-prometheus.MustNewConstMetric(c.metrics["my_counter_metric"], prometheus.CounterValue, float64(currentValue), host) 22 } 23 for host, currentValue := range mockGaugeMetricData { 24 ch <-prometheus.MustNewConstMetric(c.metrics["my_gauge_metric"], prometheus.GaugeValue, float64(currentValue), host) 25 } 26 }
4、啟動HTTP服務
http.Handle(*metricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
http.ListenAndServe(":"+*listenAddr, nil)
這只是一個Demo,當實際需要開發一個exporter時,你需要重新定義要抓取的指標,並添加采集數據的具體邏輯。