在微服务中,随着项目变得复杂,调用链也越来越复杂:比如商城服务->产品服务->库存服务等,从而导致出现问题的时候查询问题可能会变得很困难,而链路追踪可以呈现调用(依赖)关系,在开源项目中也有好几种能实现此种功能的,比如本文用到的jaeger(jaeger是实现了open tracing标准的),以及apache skywalking等:
图1 jaeger ui
下载安装jaeger opentelemetryopentelemetry是一个CNCF的项目,结合了opentracing和opencensus标准,目标是实现log、tracing、metrics统一的sdk,支持多语言,其go语言支持:https://opentelemetry.io/docs/instrumentation/go/getting-started/
What is OpenTelemetry?开始使用OpenTelemetry is a set of APIs, SDKs, tooling and integrations that are designed for the creation and management of telemetry data such as traces, metrics, and logs. The project provides a vendor-agnostic implementation that can be configured to send telemetry data to the backend(s) of your choice. It supports a variety of popular open-source projects including Jaeger and Prometheus.
引入
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/otel/exporters/stdout/stdouttrace
简单封装 jaeger.go
package jaeger
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"go.opentelemetry.io/otel/trace"
"os"
)
// tracerProvider returns an OpenTelemetry TracerProvider configured to use
// the Jaeger exporter that will send spans to the provided url. The returned
// TracerProvider will also use a Resource configured with all the information
// about the application.
func tracerProvider(url, serviceName, environment, id string) (*tracesdk.TracerProvider, error) {
// Create the Jaeger exporter
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
if err != nil {
return nil, err
}
tp := tracesdk.NewTracerProvider(
// Always be sure to batch in production.
tracesdk.WithBatcher(exp),
// Record information about this application in an Resource.
tracesdk.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(serviceName),
attribute.String("environment", environment),
attribute.String("ID", id),
)),
)
return tp, nil
}
func NewJaeger(ctx context.Context, url, serviceName, environment, id string) {
tp, err := tracerProvider(url, serviceName, environment, id)
if err != nil {
return
}
// Register our TracerProvider as the global so any imported
// instrumentation in the future will default to using it.
otel.SetTracerProvider(tp)
ctx, cancel := context.WithCancel(ctx)
sig := make(chan os.Signal, 1)
select {
case <-ctx.Done():
case <-sig:
}
if err := tp.Shutdown(ctx); err != nil {
log.Fatal(err)
}
cancel()
}
func StartFromContext(ctx context.Context, tracer, spanName string) (context.Context, trace.Span) {
tp := otel.GetTracerProvider()
t := tp.Tracer(tracer)
return t.Start(ctx, spanName)
}
在项目中使用
由于我们项目中用的是go-zero框架,是通过go-zero的中间件机制初始化的,实际中可以根据需要使用,例如在main.go中
go jaeger.NewJaeger(ctx, url, serviceName, environment, id)
其中ctx表示go 的context.Context,url是Jaeger ui地址,serviceName指的是微服务名,environment指的是运行环境如dev,id随便传
然后就可以在service里面直接使用:
_, span := jaeger.StartFromContext(ctx, "trancer", "spanname") defer span.End()
span.SetAttributes(attribute.String("data from", "redis cache"))
最后再去jaeger ui就能看到开头的效果。
参考: