谈谈链路追踪

在微服务中,随着项目变得复杂,调用链也越来越复杂:比如商城服务->产品服务->库存服务等,从而导致出现问题的时候查询问题可能会变得很困难,而链路追踪可以呈现调用(依赖)关系,在开源项目中也有好几种能实现此种功能的,比如本文用到的jaeger(jaeger是实现了open tracing标准的),以及apache skywalking等:

图1  jaeger ui

下载安装jaeger opentelemetry

opentelemetry是一个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就能看到开头的效果。

参考: