一探究竟新一代可觀測標準 OpenTelemetry

什麼是可觀測

相信大家日常經常使用kibanagrafanajaeger等平臺觀察系統狀態和定位問題,這些就是可觀測體系的一部分。可觀測主要包括:

我們排查問題過程,一般都會把三者日誌、指標、追蹤結合來看,比如通過接口異常增量指標發現問題 ---> 鏈路追蹤定位異常服務 ---> 排查異常服務日誌,所以關於可觀測我們經常可以看見這個經典的圖片:

什麼是OpenTelemetry

關於可觀測除了上述的各種實現外,還有另一套實現OpenCensus,於是誕生統一標準OpenTelemetry且兼容OpenTracingOpenCensus。不過關於 go 語言OpenTelemetry的統一 sdk 實現還不完善,比如目前還不支持日誌,具體可以查看https://github.com/open-telemetry/opentelemetry-go

接下來,我們基於 Go 來看看,原 sdk(也就是未使用OpenTelemetry) 接入指標和追蹤的方式和基於OpenTelemetry新體系的指標和追蹤接入方式區別。

可觀測之指標

基於原生promethuessdk 的指標採集演示

Go 版本 這裏用的 1.14

主要依賴的包:

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"

就依賴了兩個包,使用比較簡單:

  1. 單獨創建一個 server 使用github.com/prometheus/client_golang/prometheus/promhttp暴露指標

  2. 使用github.com/prometheus/client_golang/prometheus創建自定義指標,比如NewCounterVec創建計數器、HistogramVec創建直方圖等等。

  3. WithLabelValues給自定義指標打標籤

代碼示例如下:

docker 示例:https://github.com/TIGERB/easy-tips/tree/master/docker/grafana-promethues/go-demo/main.go.promethues

package main

import (
 "net/http"

 // 引入prometheus sdk
 "github.com/prometheus/client_golang/prometheus"
 "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
 // 自定義接口請求次數自定義指標
 GlobalApiCounter *prometheus.CounterVec
)

func init() {
 // 初始化接口請求次數自定義指標
 GlobalApiCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
  Name: "demo_api_request_counter",
  Help: "接口請求次數自定義指標",
 },
  []string{"domain", "uri"}, // 域名和接口
 )
 prometheus.MustRegister(GlobalApiCounter)
}

func main() {
 go (func() {
  // 創建一個獨立的server export暴露Go指標 避免通過業務服務暴露到外網
  metricServer := http.NewServeMux()
  metricServer.Handle("/metrics", promhttp.Handler())
  http.ListenAndServe(":2112", metricServer)
 })()

 // 使用默認server
 http.HandleFunc("/v1/demo", func(w http.ResponseWriter, r *http.Request) {
  // 自定義計數
  GlobalApiCounter.WithLabelValues(r.Host, r.RequestURI).Inc()

  w.Write([]byte("test"))
 })
 // 啓動一個http服務並監聽6060端口 這裏第二個參數可以指定handler
 http.ListenAndServe(":6060", nil)
}

訪問接口curl 127.0.0.1:6060/v1/demo後,查看指標輸出 curl 127.0.0.1:2112/metrics,如下:

# HELP demo_api_request_counter 接口請求次數自定義指標# TYPE demo_api_request_counter counterdemo_api_request_counter{doamin="127.0.0.1:6060",uri="/v1/demo"} 4

基於OpenTelemetrysdk 的promethues指標採集演示

Go 版本 這裏用的 1.19 版本太低報錯

主要依賴的包:

"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/metric"metricsdk "go.opentelemetry.io/otel/sdk/metric"

相對於原生 prome 只使用兩個包,引入的包多幾個:

  1. 相同點:仍然單獨創建一個 server 使用github.com/prometheus/client_golang/prometheus/promhttp暴露指標

  2. 不同點:使用go.opentelemetry.io/otel/exporters/prometheus初始化一個指標對象meter

  3. 不同點:使用meter.Int64Counter初始化計數器、直方圖等

  4. 不同點:metric.WithAttributes打標籤

代碼示例如下:

docker 示例:https://github.com/TIGERB/easy-tips/tree/master/docker/grafana-promethues/go-demo/main.go

package main

import (
 "context"
 "net/http"

 // 引入prometheus sdk
 "github.com/prometheus/client_golang/prometheus/promhttp"
 "go.opentelemetry.io/otel/attribute"
 "go.opentelemetry.io/otel/exporters/prometheus"
 "go.opentelemetry.io/otel/metric"
 metricsdk "go.opentelemetry.io/otel/sdk/metric"
)

var meter metric.Meter

func init() {
 // 初始化指標meter
 mexp, err := prometheus.New()
 if err != nil {
  panic(err)
 }
 meter = metricsdk.NewMeterProvider(metricsdk.WithReader(mexp)).Meter("http-demo")
}

func main() {
 // 集成指標
 // https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go
 // 創建一個接口訪問計數器
 urlCouter, _ := meter.Int64Counter("demo_api_request_counter", metric.WithDescription("QPS"))

 go (func() {
  // 創建一個獨立的server export暴露Go指標 避免通過業務服務暴露到外網
  metricServer := http.NewServeMux()
  metricServer.Handle("/metrics", promhttp.Handler())
  http.ListenAndServe(":2112", metricServer)
 })()

 // 使用默認server
 http.HandleFunc("/v1/demo", func(w http.ResponseWriter, r *http.Request) {
  // 自定義計數
  opt := metric.WithAttributes(attribute.Key("domain").String(r.Host), attribute.Key("uri").String(r.RequestURI))
  urlCouter.Add(context.Background(), 1, opt) // 計數

  w.Write([]byte("test"))
 })
 // 啓動一個http服務並監聽6060端口 這裏第二個參數可以指定handler
 http.ListenAndServe(":6060", nil)
}

訪問接口curl 127.0.0.1:6060/v1/demo後,查看指標輸出 curl 127.0.0.1:2112/metrics,如下:

# HELP demo_api_request_counter_total QPS# TYPE demo_api_request_counter_total counterdemo_api_request_counter_total{domain="127.0.0.1:6060",otel_scope_} 1

可觀測之追蹤

基於OpenTracingsdk 的jaeger追蹤演示

Go 版本 這裏用的 1.18

主要依賴的包:

"github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/transport"

使用方式:

  1. 使用jaeger.NewTracer創建一個 tracer 對象tracer,並指定 jaeger 服務地址 (這裏採用的是非 agent 方式演示)

  2. 使用tracer創建一個 spantracer.StartSpan,並在你想追蹤的代碼段後面span.Finish()

  3. 這裏上游使用grpc服務做測試,使用 opentracing 中間件 SDK go-grpc-middleware/tracing/opentracing

代碼示例如下:

docker 示例:https://github.com/TIGERB/easy-tips/tree/master/docker/go-jaeger

package main

import (
 // 導入net/http包
 "context"
 "http-demo/demov1"
 "io"
 "net/http"

 "google.golang.org/grpc"

 grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
 opentracing "github.com/opentracing/opentracing-go"
 jaeger "github.com/uber/jaeger-client-go"
 "github.com/uber/jaeger-client-go/transport"
)

var (
 // 創建一個tracer對象
 tracer opentracing.Tracer
)

func main() {
 // 指定上報數據的jaeger服務地址
 sender := transport.NewHTTPTransport(
  "http://go-jaeger-jaeger-demo:14268/api/traces",
 )
 var closer io.Closer
 tracer, closer = jaeger.NewTracer(
  "http-demo",
  jaeger.NewConstSampler(true),
  jaeger.NewRemoteReporter(sender),
 )
 defer closer.Close()

 http.HandleFunc("/v1/demo", func(w http.ResponseWriter, r *http.Request) {
  // 創建一個`span`
  span := tracer.StartSpan("demo_span_1")
  defer span.Finish()
  name, err := demoGrpcReq()
  if err != nil {
   w.Write([]byte(err.Error()))
  }
  // 寫入響應內容
  w.Write([]byte(name))
 })

 // 啓動一個http服務並監聽6060端口 這裏第二個參數可以指定handler
 http.ListenAndServe(":6060", nil)
}

func demoGrpcReq() (string, error) {
 // 使用opentracing中間件SDK go-grpc-middleware/tracing/opentracing
 conn, err := grpc.Dial("grpc-demo:1010", grpc.WithInsecure(), grpc.WithUnaryInterceptor(grpc_opentracing.UnaryClientInterceptor(
  grpc_opentracing.WithTracer(tracer),
 )))
 if err != nil {
  return "", err
 }
 defer conn.Close()

 client := demov1.NewGreeterClient(conn)
 resp, err := client.SayHello(context.TODO(), &demov1.HelloRequest{
  Name: "http request",
 })
 if err != nil {
  return "", err
 }
 return resp.GetMessage(), nil
}

使用docker-compose up -d啓動演示服務,模擬請求curl 127.0.0.1:6060/v1/demo,查看 jaeger 後臺http://localhost:16686/,示例如下:

基於OpenTelemetrysdk 的jaeger追蹤演示

Go 版本 這裏用的 1.19 版本太低報錯

主要依賴的包:

"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/semconv/v1.17.0"

使用方式:

  1. 使用"go.opentelemetry.io/otel/exporters/jaeger"初始化追蹤 tracer,同樣指定 jaeger 服務地址 (這裏採用的是非 agent 方式演示)

  2. http 服務進行鏈路追蹤,使用包go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewHandler創建 http handler

  3. grpc 服務進行鏈路追蹤,使用 grpc otel 追蹤 sdk go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc

代碼示例如下:

docker 示例:https://github.com/TIGERB/easy-tips/tree/master/docker/go-otel

package main

import (
 // 導入net/http包
 "context"
 "http-demo/demov1"
 "net/http"

 "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
 "google.golang.org/grpc"

 "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
 "go.opentelemetry.io/otel"
 "go.opentelemetry.io/otel/exporters/jaeger"
 "go.opentelemetry.io/otel/propagation"
 "go.opentelemetry.io/otel/sdk/resource"
 tracesdk "go.opentelemetry.io/otel/sdk/trace"
 semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

// 追蹤
var tracer *tracesdk.TracerProvider

func init() {
 // 初始化追蹤tracer
 // https://github.com/open-telemetry/opentelemetry-go/blob/main/example/jaeger/main.go
 // Create the Jaeger exporter
 exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://jaeger-demo:14268/api/traces")))
 if err != nil {
  panic(err)
  return
 }
 tracer = tracesdk.NewTracerProvider(
  tracesdk.WithBatcher(exp),
  tracesdk.WithResource(resource.NewWithAttributes(
   semconv.SchemaURL,
   semconv.ServiceNameKey.String("http-demo"),
  )),
 )

}

// 常見框架集成opentelemetry SDK
// https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation
func main() {
 otel.SetTracerProvider(tracer)
    // 關聯上下文
 otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
 ctx, cancel := context.WithCancel(context.Background())
 defer cancel()
 defer tracer.Shutdown(ctx)

 // /v1/demo接口 業務邏輯handler
 demoHandler := func(w http.ResponseWriter, r *http.Request) {
  // 調用demo grpc接口
  name, err := demoGrpcReq()
  if err != nil {
   w.Write([]byte(err.Error()))
  }
  // 寫入響應內容
  w.Write([]byte(name))
 }

 // 集成鏈路追蹤
 // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/net/http/otelhttp/example/server/server.go
 otelHandler := otelhttp.NewHandler(http.HandlerFunc(demoHandler), "otelhttp demo test")
 http.Handle("/v1/demo", otelHandler)

 // 啓動一個http服務並監聽6060端口 這裏第二個參數可以指定handler
 http.ListenAndServe(":6060", nil)
}

func demoGrpcReq() (string, error) {
 // 使用grpc otel追蹤sdk go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
 conn, err := grpc.Dial("grpc-demo:1010",
  grpc.WithInsecure(),
  grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
  grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
 )
 if err != nil {
  return "", err
 }
 defer conn.Close()

 client := demov1.NewGreeterClient(conn)
 resp, err := client.SayHello(context.TODO(), &demov1.HelloRequest{
  Name: "http request",
 })
 if err != nil {
  return "", err
 }
 return resp.GetMessage(), nil
}

使用docker-compose up -d啓動演示服務,模擬請求curl 127.0.0.1:6060/v1/demo,查看 jaeger 後臺http://localhost:16686/,示例如下:

總結

FiFlMl

1QIPlz

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/P-JmlfDPzcKJjsQ1M1Mo_A