1、全链路

在微服务架构中,调用链漫长而复杂的,要了解其中的每个环节及其性能,你需要全链路跟踪。 它的原理很简单,你可以在每个请求开始时生成一个唯一的ID,并将其传递到整个调用链。 该ID称为CorrelationID,你可以用它来跟踪整个请求并获得各个调用环节的性能指标
简单来说有两个问题需要解决。
第一,如何在应用程序内部传递ID;
第二,当你需要调用另一个微服务时,如何通过网络传递ID。

2、什么是OpenTracing?

现在有许多开源的分布式跟踪库可供选择,其中最受欢迎的库可能是ZipkinJaeger。 选择哪个是一个令人头疼的问题,因为你现在可以选择最受欢迎的一个,但是如果以后有一个更好的出现呢?OpenTracing可以帮你解决这个问题。它建立了一套跟踪库的通用接口,这样你的程序只需要调用这些接口而不被具体的跟踪库绑定,将来可以切换到不同的跟踪库而无需更改代码。Zipkin和Jaeger都支持OpenTracing。

3、如何跟踪服务器端点(server endpoints)?

跟踪系统中通常有四个组件,下面我用Zipkin作为示例:

(1)recorder(记录器):记录跟踪数据

(2)Reporter (or collecting agent)(报告器或收集代理):从记录器收集数据并将数据发送到UI程序

(3)Tracer:生成跟踪数据

(4)UI:负责在图形UI中显示跟踪数据

微服务控制让服务宕机 微服务 trace_github

4、跟踪类型

两种不同类型的跟踪,一种是进程内跟踪(in-process),另一种是跨进程跟踪(cross-process)。
OpenTracing中,一个重要的概念是“trace”,它表示从头到尾的一个请求的调用链,它的标识符是“traceID”。 一个“trace”包含有许多跨度(span),每个跨度捕获调用链内的一个工作单元,并由“spanId”标识。 每个跨度具有一个父跨度,并且一个“trace”的所有跨度形成有向无环图(DAG)。

5、Jaeger安装

docker run -d --name=jaeger -p6831:6831/udp -p16686:16686 jaegertracing/all-in-one:latest

浏览器Web UI: http://localhost:16686/

6、编码实现

初始化
package jaeger

import (
	"fmt"
	opentracing "github.com/opentracing/opentracing-go"
	"github.com/sirupsen/logrus"
	"github.com/uber/jaeger-client-go"
	"github.com/uber/jaeger-client-go/config"
	"lioncat/configs"
)

var globalJaeger opentracing.Tracer

const JaegerSamplerParam = 1 // 采样所有追踪(不能在online环境使用)

func InitJaegerTracer() {
	// 设置应用的的基本信息
	jaegerConf := configs.GetConfigsInfo().Jaeger
	reportingHost := fmt.Sprintf("%s:%d",jaegerConf.Host,jaegerConf.Port)

	cfg := config.Configuration{
		// 设置服务名称
		ServiceName: jaegerConf.ServiceName,

		// 设置采样
		Sampler: &config.SamplerConfig{
			Type:  "const",             // 采样类型,const:始终对所有跟踪ID做出相同的采样决策
			Param: JaegerSamplerParam,  // 将采样频率设置为 1,每一个 span 都记录,方便查看测试结果
		},

		// 设置记录
		Reporter: &config.ReporterConfig{
			LogSpans:           true,           // 启用日志记录
			LocalAgentHostPort: reportingHost,  // 上报代理地址
		},
	}

	// 创建tracer
	tracer, _, err := cfg.NewTracer(
		config.Logger(jaeger.StdLogger),
		)
	if err != nil {
		logrus.WithFields(logrus.Fields{"error": err.Error()}).Errorf("NewTracer失败")
	}

	// 设置全局的Tracer对象
	opentracing.SetGlobalTracer(tracer)

	globalJaeger = tracer
	logrus.Println("初始化Jaeger成功")
}

func GetJaegerTracer()opentracing.Tracer{
	return globalJaeger
}
中间件
package middleware

import (
	"context"
	"github.com/gin-gonic/gin"
	opentracing "github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"github.com/uber/jaeger-client-go"
	"lioncat/configs"
	ljaeger "lioncat/libs/jaeger"
	"lioncat/utils"
)

func JaegerTracing() gin.HandlerFunc{
	return func (c *gin.Context){
		// 没有开启Jaeger
		if !configs.GetConfigsInfo().Log.IsJaeger{
			_,isExist := c.Get("Trace-Id")
			if isExist == false {
				traceId := utils.GetUUID()
				c.Set("Trace-Id",traceId)
			}
			c.Next()
			return
		}
		var newCtx context.Context
		var span opentracing.Span
		// 提取 SpanContext实例
		spanCtx,err := opentracing.GlobalTracer().Extract(
			opentracing.HTTPHeaders,
			opentracing.HTTPHeadersCarrier(c.Request.Header))

		if err != nil {
			// 提取失败,生成
			span,newCtx = opentracing.StartSpanFromContextWithTracer(
				c.Request.Context(),
				ljaeger.GetJaegerTracer(),
				c.Request.URL.Path)
		} else {
			// 提取成功,增加节点
			span,newCtx = opentracing.StartSpanFromContextWithTracer(
				c.Request.Context(),
				ljaeger.GetJaegerTracer(),
				c.Request.URL.Path,
				opentracing.ChildOf(spanCtx),
				opentracing.Tag{Key: string(ext.Component),Value: "HTTP"},
				)
		}
		defer span.Finish()

		// 获取requestID
		var requestID string
		if sc, ok := span.Context().(jaeger.SpanContext); ok {
			requestID = sc.TraceID().String()
		}

		if requestID == ""{
			requestID = utils.GetUUID()
		}

		c.Set("Trace-Ctx", opentracing.ContextWithSpan(context.Background(), span))

		c.Set("Trace-Id",requestID)
		c.Request = c.Request.WithContext(newCtx)
		c.Next()
	}
}
日志写入Jaeger
package log

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/opentracing/opentracing-go"
	otlog "github.com/opentracing/opentracing-go/log"
	"github.com/sirupsen/logrus"
	"lioncat/utils"
	"runtime"
)

type JaegerLogger struct{
	log    *logrus.Logger
	span   opentracing.Span
}

func NewJaegerLogger(logger *logrus.Logger)Logger{
	return &JaegerLogger{
		log:logger,
	}
}

type logData struct{
	TranceId   string         `json:"tranceId"`
	Msg        interface{}     `json:"msg"`
	FuncName   string          `json:"funcName"`
	Line       int             `json:"line"`
}

func (l* JaegerLogger)Info(ctx *gin.Context,args ...interface{}){
	// 打印行号,函数名
	data := l.PrintLine(l.GetTranceID(ctx),3,args)

	// 链路追踪,设置span
	l.span = l.GetSpan(ctx)
	l.span = l.span.SetTag("error", false)
	l.AddSpanLogFields("info",data)

	l.log.Info(data)
}

func (l* JaegerLogger)Warn(ctx *gin.Context,args ...interface{}){
	// 打印行号,函数名
	data := l.PrintLine(l.GetTranceID(ctx),3,args)

	// 链路追踪,设置span
	l.span = l.GetSpan(ctx)
	l.span = l.span.SetTag("error", false)
	l.AddSpanLogFields("warn",data)

	l.log.Warn(data)
}

func (l* JaegerLogger)Error(ctx *gin.Context,args ...interface{}){
	// 打印行号,函数名
	data := l.PrintLine(l.GetTranceID(ctx),3,args)

	// 链路追踪,设置span
	l.span = l.GetSpan(ctx)
	l.span = l.span.SetTag("error", true)
	l.AddSpanLogFields("error",data)

	l.log.Error(data)
}

func (l* JaegerLogger)Panic(ctx *gin.Context,args ...interface{}){
	// 打印行号,函数名
	data := l.PrintLine(l.GetTranceID(ctx),3,args)

	// 链路追踪,设置span
	l.span = l.GetSpan(ctx)
	l.span = l.span.SetTag("error", true)
	l.AddSpanLogFields("panic",data)

	l.log.Panic(data)
}

func (l* JaegerLogger)Infof(ctx *gin.Context,format string, args ...interface{}){
	msg := fmt.Sprintf(format,args...)
	l.Info(ctx,msg)
}

func (l* JaegerLogger)Warnf(ctx *gin.Context,format string, args ...interface{}){
	msg := fmt.Sprintf(format,args...)
	l.Warn(ctx,msg)
}

func (l* JaegerLogger)Errorf(ctx *gin.Context,format string, args ...interface{}){
	msg := fmt.Sprintf(format,args...)
	l.Error(ctx,msg)
}

func (l* JaegerLogger)Panicf(ctx *gin.Context,format string, args ...interface{}){
	msg := fmt.Sprintf(format,args...)
	l.Panic(ctx,msg)
}

func (l* JaegerLogger)ResultError(ctx *gin.Context,args ...interface{}){
	l.Error(ctx,args)
}

// PrintLine 打印行号
func (l* JaegerLogger)PrintLine(tranceId string,skip int,args interface{})string{
	pc, _, line, _ := runtime.Caller(skip)

	data := logData{
		TranceId: tranceId,
		Msg:     args,
		FuncName: runtime.FuncForPC(pc).Name(),
		Line:     line,
	}
	byteData,_ := json.Marshal(data)
	return string(byteData)
}

// GetTranceID 获取tranceID
func (l* JaegerLogger)GetTranceID(ctx *gin.Context)string{
	tranceId,isExist := ctx.Get("Trace-Id")
	if isExist == false {
		traceId := utils.GetUUID()
		ctx.Set("Trace-Id",traceId)
		return traceId
	}
	return tranceId.(string)
}

// GetSpan 获取Span
func (l* JaegerLogger)GetSpan(ctx *gin.Context)opentracing.Span{
	traceCtx,isExist := ctx.Get("Trace-Ctx")
	if isExist == false {
		return nil
	}
	if span := opentracing.SpanFromContext(traceCtx.(context.Context)); span != nil {
		return span
	}
	return nil
}

// AddSpanLogFields 添加span log
func (l* JaegerLogger)AddSpanLogFields(level,msg string){

	if l.span == nil{
		return
	}
	l.span.LogFields(
		otlog.String("level", level),
		otlog.String("message", msg),
	)
}