前言
Opentelemetry
分散式鏈路跟蹤( Distributed Tracing
)的概念最早是由 Google 提出來的,發展至今技術已經比較成熟,也是有一些協議標準可以參考。目前在 Tracing
技術這塊比較有影響力的是兩大開源技術框架:Netflix 公司開源的 OpenTracing
和 Google 開源的 OpenCensus
。兩大框架都擁有比較高的開發者群體。為形成統一的技術標準,兩大框架最終磨合成立了 OpenTelemetry
專案,簡稱 otel
。otel 有鏈路追蹤和監控告警兩大塊,關於監控告警,可以檢視另一篇文章:Go 鏈路追蹤入門 Opentelemetry
Prometheus
Prometheus
源自 SoundCloud,擁有一整套開源系統監控和警報工具包,是支援 OpenTelemetry
的系統之一,是 CNCF 的第二個專案。
Grafana
Grafana 是一個開源的分析和視覺化平臺,它允許你查詢、視覺化和警報來自各種資料來源的資料。它提供了一個使用者友好的介面,用於建立和共享儀表板、圖表和警報。Grafana 支援廣泛的資料來源,其中就包括 Prometheus
基礎概念
這裡為了簡單入門,儘量簡單的介紹一些抽象概念,結合著程式碼理解,如果不能理解也沒關係,程式碼寫著寫著自然就明白了:
Meter Provider
用於介面化管理全域性的 Meter
建立,相當於全域性的監控指標管理工廠。
Meter
用於介面化建立並管理全域性的 Instrument
,不同的 Meter
可以看做是不同的程式元件。
Instrument
用於管理不同元件下的各個不同型別的指標,例如 http.server.request.total
Measurements
對應指標上報的具體的 DataPoint
指標資料,是一系列的數值項。
Metric Reader
用於實現對指標的資料流讀取,內部定義了具體操作指標的資料結構。OpenTelemetry
官方社群提供了多種靈活的 Reader
實現,例如 PeridRader、ManualReader
等。
Metric ExporterExporter
用於暴露本地指標到對應的第三方廠商,例如:Promtheus、Zipkin
等。
指標型別
OpenTelemetry metrics
有許多不同指標型別,可以把它想象成類似於 int, float
這種的變數型別:
Counter:只增不減的指標,比如 http
請求總數,位元組大小;
Asynchronous Counter:非同步 Counter;
UpDownCounter:可增可減的指標,比如 http
活動連線數;
Asynchronous UpDownCounter:非同步 Counter;
Gauge:可增可減的指標,瞬時計量的值,比如 CPU
使用,它是非同步的;
Histogram:分組聚合指標,這個較為難以理解一些,可以移步此處檢視,當然,後文也會有一個詳細的例子來使用它。
實戰:採集指標
廢話了一堆,終於可以實戰了。我們先以 http
請求總數為例來走一遍整個採集指標流程。安裝擴充套件:
go get github.com/prometheus/client_golang
go get go.opentelemetry.io/otel/exporters/prometheus
go get go.opentelemetry.io/otel/metric
go get go.opentelemetry.io/otel/sdk/metric
開啟 main.go
,編寫以下程式碼:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/exporters/prometheus"
api "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric"
)
const meterName = "oldme_prometheus_testing"
var (
requestHelloCounter api.Int64Counter
)
func main() {
ctx := context.Background()
// 建立 prometheus 匯出器
exporter, err := prometheus.New()
if err != nil {
log.Fatal(err)
}
// 建立 meter
provider := metric.NewMeterProvider(metric.WithReader(exporter))
meter := provider.Meter(meterName)
// 建立 counter 指標型別
requestHelloCounter, err = meter.Int64Counter("requests_hello_total")
if err != nil {
log.Fatal(err)
}
go serveMetrics()
ctx, _ = signal.NotifyContext(ctx, os.Interrupt)
<-ctx.Done()
}
func serveMetrics() {
log.Printf("serving metrics at localhost:2223/metrics")
http.Handle("/metrics", promhttp.Handler())
http.Handle("/index", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 記錄 counter 指標
requestHelloCounter.Add(r.Context(), 1)
_, _ = w.Write([]byte("Hello, Otel!"))
}))
err := http.ListenAndServe(":2223", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts.
if err != nil {
fmt.Printf("error serving http: %v", err)
return
}
}
在我們的程式碼中,我們定義一個名字為 requests_hello_total
的 Int64Counter
指標型別,Int64Counter
代表這是一個只增不減的 int64
數值,用作記錄請求總數正好合適。執行我們的程式,如果不出錯的話,訪問 http://localhost:2223/index 可以看到 Hello, Otel!
。並且我們訪問 http://localhost:2223/metrics 可以看到指標資料:
這裡資料還沒有進行視覺化,我們先把流程走通,多訪問幾次 http://localhost:2223/index 可以看到 requests_hello_total
會增加:
Histogram
接下來我們採集一下 Histogram
指標,統計在 0.1, 0.2, 0.5, 1, 2, 5
秒以內的 http
請求數,在 main.go
中加上相關程式碼,可以直接複製過去:
package main
import (
"context"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"os/signal"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/exporters/prometheus"
api "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric"
)
const meterName = "oldme_prometheus_testing"
var (
requestHelloCounter api.Int64Counter
requestDurationHistogram api.Float64Histogram
)
func main() {
ctx := context.Background()
// 建立 prometheus 匯出器
exporter, err := prometheus.New()
if err != nil {
log.Fatal(err)
}
// 建立 meter
provider := metric.NewMeterProvider(metric.WithReader(exporter))
meter := provider.Meter(meterName)
// 建立 counter 指標型別
requestHelloCounter, err = meter.Int64Counter("requests_hello_total")
if err != nil {
log.Fatal(err)
}
// 建立 Histogram 指標型別
requestDurationHistogram, err = meter.Float64Histogram(
"request_hello_duration_seconds",
api.WithDescription("記錄 Hello 請求的耗時統計"),
api.WithExplicitBucketBoundaries(0.1, 0.2, 0.5, 1, 2, 5),
)
if err != nil {
log.Fatal(err)
}
go serveMetrics()
go goroutineMock()
ctx, _ = signal.NotifyContext(ctx, os.Interrupt)
<-ctx.Done()
}
func serveMetrics() {
log.Printf("serving metrics at localhost:2223/metrics")
http.Handle("/metrics", promhttp.Handler())
http.Handle("/index", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 記錄 counter 指標
requestHelloCounter.Add(r.Context(), 1)
// 計算請求處理時間
startTime := time.Now()
// 模擬請求處理時間
time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
defer func() {
duration := time.Since(startTime).Seconds()
requestDurationHistogram.Record(r.Context(), duration)
}()
_, _ = w.Write([]byte("Hello, Otel!"))
}))
err := http.ListenAndServe(":2223", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts.
if err != nil {
fmt.Printf("error serving http: %v", err)
return
}
}
// 隨機模擬若干個協程
func goroutineMock() {
for {
go func() {
// 等待若干秒
var s = time.Duration(rand.Intn(10))
time.Sleep(s * time.Second)
}()
time.Sleep(1 * time.Millisecond)
}
}
走到這裡,程式碼層面結束了,已經成功一半了,程式碼開源在 Github。之後我們就可以安裝 Prometheus
服務端和 Grafana
來進行資料視覺化。
安裝 Prometheus
Prometheus
有多種安裝方式,我這裡依舊採用 Docker
安裝,當然,你也可以使用其他方式安裝,具體安裝方式可以參考其他文章,後續 Grafana
同理,不在贅述,在 Prometheus.yml
中填寫 targets
我們的地址:
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:2223"]
Prometheus
會自動去 {{target}}/metrics
中拉取我們的指標。之後在瀏覽器開啟 Promethues
的地址,例如我的是:http://localhost:9090,如果全部正常的話可以在 status:targets
中看見我們的指標:
在 Promethues
的首頁查詢 requests_hello_total
指標可以看到視覺化的圖表:
安裝 Grafana
我的 Grafana
安裝好了,登入進去後是這樣的(我更改後預設顏色):
在 Data source
中新增 Prometheus
伺服器,然後在 Dashboard
中新增我們想要監控的指標,即可看到更美觀的圖表: