1、全链路
在微服务架构中,调用链
是漫长而复杂
的,要了解其中的每个环节及其性能,你需要全链路跟踪
。 它的原理很简单,你可以在每个请求
开始时生成一个唯一的ID
,并将其传递到整个调用链
。 该ID称为CorrelationID
,你可以用它来跟踪整个请求并获得各个调用环节的性能指标
。
简单来说有两个问题
需要解决。
第一,如何在应用程序内部传递ID;
第二,当你需要调用另一个微服务时,如何通过网络传递ID。
2、什么是OpenTracing?
现在有许多开源的分布式跟踪库可供选择,其中最受欢迎的库可能是Zipkin
和Jaeger
。 选择哪个是一个令人头疼的问题,因为你现在可以选择最受欢迎的一个,但是如果以后有一个更好的出现呢?OpenTracing
可以帮你解决这个问题。它建立了一套跟踪库的通用接口
,这样你的程序只需要调用这些接口
而不被具体的跟踪库绑定,将来可以切换到不同的跟踪库而无需更改代码。Zipkin和Jaeger都支持OpenTracing。
3、如何跟踪服务器端点(server endpoints)?
跟踪系统中通常有四个组件,下面我用Zipkin作为示例:
(1)recorder(记录器):记录跟踪数据
(2)Reporter (or collecting agent)(报告器或收集代理):从记录器收集数据并将数据发送到UI程序
(3)Tracer:生成跟踪数据
(4)UI:负责在图形UI中显示跟踪数据
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),
)
}