昨天這樣安裝完, 跑了案例, 會發現Jaeger UI上有個System Architecture
按鈕.
但點下去沒東西.
Day26提到, OpenTelemetry有一個重點是Observability可觀察性
.
為了讓我們清楚各服務的調用關係.
需要多加裝另一樣服務Spark-Dependencies.
docker run --net=jaeger_jaeger_net --env STORAGE=elasticsearch --env ES_NODES=http://172.16.230.100:9200,http://172.16.230.101:9201 jaegertracing/spark-dependencies
20/10/04 12:56:33 INFO ElasticsearchDependenciesJob: Running Dependencies job for 2020-10-04T00:00Z, reading from jaeger-span-2020-10-04 index, result storing to jaeger-dependencies-2020-10-04
20/10/04 12:56:35 INFO ElasticsearchDependenciesJob: Done, 1 dependency objects created
搭配排程腳本, 就能定期的從DB中收集Span, 分析各服務之間的鍊結, 並且將分析結果寫入DB.
因為DB資料很多, 該隻服務只會根據UTC時間處理當日資料.
所以每日跑依次是比較適當的.
因為昨天那隻example...就本地自己呼叫, 箭頭上的數字代表該次調用的呼叫次數.
Reference
來把這裡面的示範給改一下.
config.go
裡面原本用stdout的exporter, 這裡改成昨天玩的Jaeger Exporter
package config
import (
"log"
"go.opentelemetry.io/otel/api/global"
// "go.opentelemetry.io/otel/exporters/stdout"
"go.opentelemetry.io/otel/exporters/trace/jaeger"
"go.opentelemetry.io/otel/label"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
// Init configures an OpenTelemetry exporter and trace provider
func InitServerTracer() func() {
provder, flush, err := jaeger.NewExportPipeline(jaeger.WithAgentEndpoint("172.16.230.5:6831"), jaeger.WithProcess(jaeger.Process{
ServiceName: "trace-grpc-server",
Tags: []label.KeyValue{
label.String("exporter", "jaeger"),
label.Float64("float", 312.23),
},
}),
jaeger.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
)
if err != nil {
log.Fatal(err)
}
global.SetTracerProvider(provder)
return func() {
flush()
}
}
// Init configures an OpenTelemetry exporter and trace provider
func InitClientTracer() func() {
provder, flush, err := jaeger.NewExportPipeline(jaeger.WithAgentEndpoint("172.16.230.5:6831"), jaeger.WithProcess(jaeger.Process{
ServiceName: "trace-grpc-client",
Tags: []label.KeyValue{
label.String("exporter", "jaeger"),
label.Float64("float", 312.23),
},
}),
jaeger.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
)
if err != nil {
log.Fatal(err)
}
global.SetTracerProvider(provder)
return func() {
flush()
}
}
server main.go
server改成呼叫config.InitServerTracer()
並且在SayHello(), 內部去呼叫DB.
對DB這裡開個Span, 用提到的Attributes, 紀錄一下參數.
這裡給出 有差異的部份代碼
import (
"context"
"fmt"
"io"
"log"
"net"
"time"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/api"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/config"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/label"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"database/sql"
"github.com/go-sql-driver/mysql"
)
// SayHello implements api.HelloServiceServer
func (s *server) SayHello(ctx context.Context, in *api.HelloRequest) (*api.HelloResponse, error) {
log.Printf("Received: %v\n", in.GetGreeting())
time.Sleep(50 * time.Millisecond)
tracer := global.Tracer("trace-grpc-server")
var span trace.Span
_, span = tracer.Start(ctx, "db_dt_teacher")
span.SetAttributes(label.String("Tname", "kobe"))
if _, err := dbClient.Query("SELECT * FROM dt_teacher WHERE Tname=?;", "kobe"); err != nil {
span.End()
return &api.HelloResponse{Reply: "Hello " + in.Greeting}, err
}
span.End()
return &api.HelloResponse{Reply: "Hello " + in.Greeting}, nil
}
var dbClient *sql.DB
func main() {
var err error
fn := config.InitServerTracer()
defer fn()
dbConfig := mysql.Config{
User: "root",
Passwd: "m_root_pwd",
Addr: "172.31.0.11:3306",
Net: "tcp",
DBName: "exam",
AllowNativePasswords: true,
}
if dbClient, err = sql.Open("mysql", dbConfig.FormatDSN()); err != nil {
log.Println(err)
}
defer dbClient.Close()
s := grpc.NewServer(
grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(global.Tracer(""))),
grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(global.Tracer(""))),
)
}
client main.go
這裡變化比較少, 只有改到main.
這裡跟server的部份都出現
grpc.WithUnaryInterceptor跟grpc.WithStreamInterceptor.
這其實就是grpc框架內的middleware.
本來會呼叫很多個grpc method.
我把另外三個都註解掉了.
func main() {
var err error
fn := config.InitClientTracer()
defer fn()
var conn *grpc.ClientConn
conn, err = grpc.Dial(":7777", grpc.WithInsecure(),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(global.Tracer(""))),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(global.Tracer(""))),
)
if err != nil {
log.Fatalf("did not connect: %s", err)
}
defer func() { _ = conn.Close() }()
c := api.NewHelloServiceClient(conn)
callSayHello(c)
//callSayHelloClientStream(c)
//callSayHelloServerStream(c)
//callSayHelloBidiStream(c)
time.Sleep(10 * time.Millisecond)
}
依序執行server內的main.go -> client內的main.go
這圖能看到Client先SENT, 再來才收到RECEIVED
Server則是倒過來.
這裡就能看到我塞的Attribute Tname.
接著執行上面的Spark-Dependencies
這就不難解釋了.
Client呼叫了1次Server, Server有呼叫1次Span DB, 因為Tracer是一樣的, 所以都指向自己.
HotROD
是一組範例, 內部寫好了幾個微服務, 主要是講解一些Tracing API的用法跟Jaeger的使用.
Jaeger提供三種採樣
Distributed Tracing分布式鍊路追蹤在微服務架構中, 變得越來越重要.
現在蠻多微服務框架都有整合了Trace模組, 像是Go-Micro、Go-Kit都有.
因為只有透過鍊路追蹤才方便快速發現錯誤根源, 以及監控分析每條請求鍊路上的性能瓶頸.
這裡我少講的部份是Metric, 但OpenTelemetry對Metric這塊還不夠完善, 目前公司都還是借助Prometheus, 期待大一統的那日.