golang-Trace链路跟踪实践

Tracing 系统是监控的一个方面

基础层面跟踪

有很多工具是可以在 k8s 这种容器化环境里自动追踪到, 服务间的调用的. 甚至记录下一些性能相关的信息, 并生成链路的图表

Tracing 系统 侧重

  • ELK/EFK - 侧重 log
  • Promethuse - 侧重 metrics
  • Tracing 侧重链路, 兼顾 log 和 metrics

Golang 生态下的例子

初试设定

  • 一个 backend 组合服务
  • 一个 jwt auth 服务

— Login 接口会根据 userID 生成 jwt token
我们就跟踪这个路径

创建 tracer

package tracing

import (
"fmt"
"io"

opentracing "github.com/opentracing/opentracing-go"
jaeger "github.com/uber/jaeger-client-go"
config "github.com/uber/jaeger-client-go/config"
)

var (
// Tracer - tracer instance
Tracer opentracing.Tracer
// Closer - Closer instance
Closer io.Closer
)

// Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout.
func Init(service string) (opentracing.Tracer, io.Closer) {
cfg, err := config.FromEnv()
if err != nil {
panic(fmt.Sprintf("ERROR: cannot init Jaeger Conf from ENV: %v\n", err))
}
cfg.Sampler = &config.SamplerConfig{
Type: "const",
Param: 1,
}
cfg.Reporter = &config.ReporterConfig{
LogSpans: true,
}

tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
if err != nil {
panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
}
Tracer, Closer = tracer, closer
// in order to use StartSpanFromContext, we need SetGlobalTracer
opentracing.SetGlobalTracer(Tracer)
return tracer, closer
}

在 main 里初始化
确定 Service Name

tracing.Init("todo-backend-service")
defer tracing.Closer.Close()

创建 span

func LoginHandler(c *gin.Context) {
tracer := tracing.Tracer
span := tracer.StartSpan("LoginHandler")
defer span.Finish()
....
}

获取 userID, 子 span

ctx := opentracing.ContextWithSpan(context.Background(), span)
// 此处模拟检查用户,获取uid过程
user.NewUID(ctx)
// NewUID - generate uid for a user
func (u *User) NewUID(ctx context.Context) {
span, _ := opentracing.StartSpanFromContext(ctx, "NewUID")
defer span.Finish()
u.ID = strconv.Itoa(rand.New(rand.NewSource(time.Now().UnixNano())).Int())
}

1. 通过 http 调用 auth  服务

tracing 的相关信息是在 header 里带给目标服务的

ext.SpanKindRPCClient.Set(span)
ext.HTTPUrl.Set(span, authGetTokenURL)
ext.HTTPMethod.Set(span, "POST")
span.Tracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(header),
)

2. 通过 grpc 调用 auth  服务

初始化 grpc conn, 注意使用 otgrpc


//InitAuthRPC -
func InitAuthRPC(tracer opentracing.Tracer) *grpc.ClientConn {
var err error
log.Info("grpc addr", zap.String("addr", authRPCServiceURL))
conn, err = grpc.Dial(authRPCServiceURL, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(2*time.Second), grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(tracer)), grpc.WithStreamInterceptor(
otgrpc.OpenTracingStreamClientInterceptor(tracer)))
if err != nil {
log.Fatal("did not connect", zap.String("err", err.Error()))
}
return conn
}
  • … 生成 protobuf
  • …生成 go 文件
  • …调用
  • otgrpc 会帮助把 trace 信息  序列化反序列,已保持连贯

被调用 Auth 端 http

Extract 出 tracer

tracer := tracing.Tracer
spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
span := tracer.StartSpan("JWTNewTokenHandler", ext.RPCServerOption(spanCtx))
fmt.Println(c.Request.Header)
defer span.Finish()

被调用 RPC

StartSpanFromContext

span, childCtx := opentracing.StartSpanFromContext(ctx, "SayHello")
defer span.Finish()
span.SetTag("UID", in.Name)

设置 tag

span.SetTag("UID", reqParams.ID)

携带 log

span.LogKV("event", "jwt success", "token", token)

跟踪后台 Jaeger

使用 docker 或者二进制直接启动

IMG

 - 链路记录

  • RPC 记录详情

  • HTTP 记录详情

  • 比较 记录详情

我们可以明显看到 rpc 方式在性能上的优势…