使用Prometheus搞定微服務監(jiān)控
最近對服務進行監(jiān)控,而當前監(jiān)控最流行的數(shù)據(jù)庫就是 Prometheus,同時 go-zero 默認接入也是這款數(shù)據(jù)庫。今天就對 go-zero 是如何接入 Prometheus ,以及開發(fā)者如何自己定義自己監(jiān)控指標。
監(jiān)控接入
go-zero 框架中集成了基于 prometheus 的服務指標監(jiān)控。但是沒有顯式打開,需要開發(fā)者在 config.yaml 中配置:
Prometheus:
Host: 127.0.0.1
Port: 9091
Path: /metrics
如果開發(fā)者是在本地搭建 Prometheus,需要在 Prometheus 的配置文件 prometheus.yaml 中寫入需要收集服務監(jiān)控信息的配置:
- job_name: 'file_ds'
static_configs:
- targets: ['your-local-ip:9091']
labels:
job: activeuser
app: activeuser-api
env: dev
instance: your-local-ip:service-port
因為本地是用 docker 運行的。將 prometheus.yaml 放置在 docker-prometheus 目錄下:
docker run \
-p 9090:9090 \
-v dockeryml/docker-prometheus:/etc/prometheus \
prom/prometheus
打開 localhost:9090 就可以看到:

點擊 http://service-ip:9091/metrics 就可以看到該服務的監(jiān)控信息:

上圖我們可以看出有兩種 bucket,以及 count/sum 指標。
那 go-zero 是如何集成監(jiān)控指標?監(jiān)控的又是什么指標?我們?nèi)绾味x我們自己的指標?下面就來解釋這些問題
以上的基本接入,可以參看我們的另外一篇:https://zeromicro.github.io/go-zero/service-monitor.html
如何集成
上面例子中的請求方式是 HTTP,也就是在請求服務端時,監(jiān)控指標數(shù)據(jù)不斷被搜集。很容易想到是 中間件 的功能,具體代碼:https://github.com/tal-tech/go-zero/blob/master/rest/handler/prometheushandler.go。
var (
metricServerReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
...
// 監(jiān)控指標
Labels: []string{"path"},
// 直方圖分布中,統(tǒng)計的桶
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000},
})
metricServerReqCodeTotal = metric.NewCounterVec(&metric.CounterVecOpts{
...
// 監(jiān)控指標:直接在記錄指標 incr() 即可
Labels: []string{"path", "code"},
})
)
func PromethousHandler(path string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 請求進入的時間
startTime := timex.Now()
cw := &security.WithCodeResponseWriter{Writer: w}
defer func() {
// 請求返回的時間
metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), path)
metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code))
}()
// 中間件放行,執(zhí)行完后續(xù)中間件和業(yè)務邏輯。重新回到這,做一個完整請求的指標上報
// [??:洋蔥模型]
next.ServeHTTP(cw, r)
})
}
}
其實整個很簡單:
HistogramVec負責請求耗時搜集:bucket存放的就是option指定的耗時指標。某個請求耗時多少就會被聚集對應的桶,計數(shù)。最終展示的就是一個路由在不同耗時的分布,很直觀提供給開發(fā)者可以優(yōu)化的區(qū)域。 CounterVec負責指定labels標簽搜集:Labels: []string{"path", "code"}labels相當一個tuple。go-zero是以(path, code)作為整體,記錄不同路由不同狀態(tài)碼的返回次數(shù)。如果4xx,5xx過多的時候,是不是應該看看你的服務健康程度?
如何自定義
go-zero 中也提供了 prometheus metric 基本封裝,供開發(fā)者自己開發(fā)自己 prometheus 中間件。
代碼:https://github.com/tal-tech/go-zero/tree/master/core/metric
| 名稱 | 用途 | 搜集函數(shù) |
|---|---|---|
| CounterVec | 單一的計數(shù)。用作:QPS統(tǒng)計 | CounterVec.Inc() 指標+1 |
| GuageVec | 單純指標記錄。適用于磁盤容量,CPU/Mem使用率(可增加可減少) | GuageVec.Inc()/GuageVec.Add() 指標+1/指標加N,也可以為負數(shù) |
| HistogramVec | 反應數(shù)值的分布情況。適用于:請求耗時、響應大小 | HistogramVec.Observe(val, labels) 記錄指標當前對應值,并找到值所在的桶,+1 |
另外對
HistogramVec.Observe()做一個基本分析:我們其實可以看到上圖每個
HistogramVec統(tǒng)計都會有3個序列出現(xiàn):
_count:數(shù)據(jù)個數(shù)_sum:全部數(shù)據(jù)加和_bucket{le=a1}:處于[-inf, a1]的數(shù)據(jù)個數(shù)所以我們也猜測在統(tǒng)計過程中,分3種數(shù)據(jù)進行統(tǒng)計:
// 基本上在prometheus的統(tǒng)計都是使用 atomic CAS 方式進行計數(shù)的
// 性能要比使用 Mutex 要高
func (h *histogram) observe(v float64, bucket int) {
n := atomic.AddUint64(&h.countAndHotIdx, 1)
hotCounts := h.counts[n>>63]
if bucket < len(h.upperBounds) {
// val 對應數(shù)據(jù)桶 +1
atomic.AddUint64(&hotCounts.buckets[bucket], 1)
}
for {
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
// sum指標數(shù)值 +v(畢竟是總數(shù)sum)
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
break
}
}
// count 統(tǒng)計 +1
atomic.AddUint64(&hotCounts.count, 1)
}
所以開發(fā)者想定義自己的監(jiān)控指標:
在使用 goctl生成API代碼指定要生成的 中間件:https://zeromicro.github.io/go-zero/middleware.html在中間件文件書寫自己需要統(tǒng)計的指標邏輯 當然,開發(fā)者也可以在業(yè)務邏輯中書寫統(tǒng)計的指標邏輯。同上。
上述都是針對 HTTP 部分邏輯的解析,RPC 部分的邏輯類似,你可以在 攔截器 部分看到設(shè)計。
總結(jié)
本文分析了 go-zero 服務監(jiān)控指標的邏輯,當然對于一些基礎(chǔ)設(shè)施的監(jiān)控,prometheus 可以通過引入對應的 exporter 來完成。
推薦閱讀
