目錄
3 Prometheus以輪詢的方式Pull拉取Metrics
4.2.3 counter.go WithLabelValues方法
4.2.4 counter.go GetMetricWithLabelValues方法
4.2.5 vec.go GetMetricWithLabelValues方法
4.2.6 metricMap的結構,Metric最終存到一個map裏,key=根據label值計算出的hash值,value=Metric元信息
1 背景
我們想要提高微服務系統的可觀察性,因此在go語言寫的微服務中,使用Prometheus提供的go client實現上報metrics的功能。
2 什麼是Exporter?
廣義上講,所有可以向Prometheus提供監控樣本數據的程序都可以被稱爲一個Exporter。
而Exporter的一個實例稱爲target,如下所示,Prometheus通過輪詢的方式定期從這些target中獲取樣本數據。
例如我有個微服務是用go語言寫的,並且這個微服務部署了兩個實例,且在每個實例中都對外提供了一個HTTP接口"/Metrics",然後Prometheus可以通過這個HTTP接口訪問到該實例上的Metrics信息。
在這個例子中,go代碼裏的HTTP接口"/Metrics"的相關代碼就是一個Exporter,而每個微服務實例中的這個HTTP接口就是一個target。
3 Prometheus以輪詢的方式Pull拉取Metrics
Prometheus如何獲取target裏的Metrics信息?
Prometheus整體架構是以Pull的形式獲取Metrics信息,因此它會以輪詢的方式,從target那獲取Metrics信息,例如訪問target對外暴露的HTTP接口獲取Metrics信息。
4 Target是如何在本地存儲Metrics的?
我們以Counter類型的Metric爲例。
4.1 基於Go Client開發的Exporter
package main
import (
"github.com/prometheus/client_golang/prometheus"
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
"time"
"math/rand"
"fmt"
)
// Counter類型的Metric
var httpRequestCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_request_count", // Metric的name
Help: "http request count"}, // Metric的說明信息
[]string{"endpoint"}) // Metric有一個Label,名稱是endpoint,Metric形如 http_request_count(endpoint="")
// Gauge類型的Metric
var orderNum = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "order_num",
Help: "order num"})
// Summary類型的Metric
var httpRequestDuration = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "http_request_duration",
Help: "http request duration",
},
[]string{"endpoint"},
)
// 將Metric註冊到本地的Prometheus
func init() {
prometheus.MustRegister(httpRequestCount)
prometheus.MustRegister(orderNum)
prometheus.MustRegister(httpRequestDuration)
}
func main() {
// Exporter
http.Handle("/metrics", promhttp.Handler()) // 對外暴露metrics接口,等待Prometheus來拉取
http.HandleFunc("/hello/", hello) // 處理業務請求,並變更Metric信息
ipport := "127.0.0.1:8888"
fmt.Println("服務器啓動%s", ipport)
err := http.ListenAndServe(ipport, nil)
if err != nil {
fmt.Println(err)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Printf("process one request = %s\n", r.URL.Path)
// Counter類型的Metric只能增
httpRequestCount.WithLabelValues(r.URL.Path).Inc()
start := time.Now()
n := rand.Intn(100)
// Gauge類型的Metric可增可減
if n >= 90 {
orderNum.Dec()
time.Sleep(100 * time.Millisecond)
} else {
orderNum.Inc()
time.Sleep(50 * time.Millisecond)
}
// Summary類型Metric
elapsed := (float64)(time.Since(start) / time.Millisecond)
httpRequestDuration.WithLabelValues(r.URL.Path).Observe(elapsed)
w.Write([]byte("ok"))
}
4.2 Counter類型Metric源碼分析
4.2.1 聲明Counter類型變量
// Counter類型的Metric
var httpRequestCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_request_count", // Metric的name
Help: "http request count"}, // Metric的說明信息
[]string{"endpoint"}) // Metric有一個Label,名稱是endpoint,Metric形如 http_request_count(endpoint="")
4.2.2 Counter類型定義
// Counter定義
type CounterVec struct {
*MetricVec
}
// MetricVec定義
type MetricVec struct {
*metricMap // Metric最終是存在這裏
curry []curriedLabelValue
// hashAdd and hashAddByte can be replaced for testing collision handling.
hashAdd func(h uint64, s string) uint64
hashAddByte func(h uint64, b byte) uint64
}
4.2.3 counter.go WithLabelValues方法
重點:
一個指標由Metric name + Labels共同確定。
若Metric name相同,但Label的值不同,則是不同的Metric。
例如:http_request_count(endpoint="hello"),http_request_count(endpoint="world")是兩個不同的指標
// @Param lvs 表示label values
func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
c, err := v.GetMetricWithLabelValues(lvs...) // 根據label的值來找對應的Metric
if err != nil {
panic(err)
}
return c
}
4.2.4 counter.go GetMetricWithLabelValues方法
// 根據label的值來找對應的Metric
// @Param lvs表示label value
func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
if metric != nil {
return metric.(Counter), err
}
return nil, err
}
4.2.5 vec.go GetMetricWithLabelValues方法
// 根據label值找對應的metric
func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
h, err := m.hashLabelValues(lvs) // 獲取label對應的hash值,非重點不展開講,這塊的核心是,若hash值一樣,則對應的Metric是同一個
if err != nil {
return nil, err
}
// 根據hash值從metricMap裏get對應的metric
// 若不存在則新創建一個metric並放入到metricMap裏
return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil
}
4.2.6 metricMap的結構,Metric最終存到一個map裏,key=根據label值計算出的hash值,value=Metric元信息
// metricMap定義,Exporter的Metric都存在這個結構中
type metricMap struct {
mtx sync.RWMutex // Protects metrics.
metrics map[uint64][]metricWithLabelValues // Metric最終存到一個map裏,key=根據label值計算出的hash值,value=Metric元信息
desc *Desc
newMetric func(labelValues ...string) Metric
}
type metricWithLabelValues struct {
values []string // label的值
metric Metric // Metric的meta信息
}
5 Prometheus拉取Exporter的哪些數據?
// Prometheus拉取的入口
http.Handle("/metrics", promhttp.Handler())
// http.go promhttp.Handler()
func Handler() http.Handler {
return InstrumentMetricHandler(
prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}),
)
}
// http.go HandlerFor
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
// 省略部分代碼
mfs, err := reg.Gather() // 收集Metric信息
// 省略部分代碼
}
// prometheus.DefaultGatherer
// registry.go
var (
defaultRegistry = NewRegistry() // DefaultGatherer就是defaultRegistry
DefaultRegisterer Registerer = defaultRegistry
DefaultGatherer Gatherer = defaultRegistry
)
// registry.go
// Gather implements Gatherer. 負責收集metrics信息
func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
// 省略部分代碼
// 聲明Counter類型的Metric後,需要MustRegist註冊到Registry,最終就是保存在collectorsByID裏
for _, collector := range r.collectorsByID {
checkedCollectors <- collector
}
// 省略部分代碼
collectWorker := func() {
for {
select {
case collector := <-checkedCollectors:
collector.Collect(checkedMetricChan) // 執行Counter的Collect,見下文
case collector := <-uncheckedCollectors:
collector.Collect(uncheckedMetricChan)
default:
return
}
wg.Done()
}
}
// 省略部分代碼
}
// vec.go
// Collect implements Collector.
func (m *MetricVec) Collect(ch chan<- Metric) { m.metricMap.Collect(ch) }
// vec.go
// Collect implements Collector.
// 返回metricMap裏所有的Metric
func (m *metricMap) Collect(ch chan<- Metric) {
m.mtx.RLock()
defer m.mtx.RUnlock()
for _, metrics := range m.metrics {
for _, metric := range metrics {
ch <- metric.metric
}
}
}
至此,可見Prometheus拉取的就是Counter類型的metricMap裏的 metric數據。
這裏有一點要注意:Prometheus拉取metric後並沒有刪除Local的metric信息。