gin集成jaeger中间件实现链路追踪
gin集成jaeger中间件实现链路追踪_jaeger链路跟踪-CSDN博客
2. 技术选型
2.1 方案对比
图【1】来自网络,请自行对比验证
图 1
2.2 选型
本项目基于golang和gin框架,以及链路中间件对比,选择jaeger作为工具进行集成。
3. 核心实现
3.1 jaeger服务搭建
这里就借助网上的all-in-one的docker方式搭建即可,无需多费力。
docker run --rm --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 14250:14250 \
-p 14268:14268 \
-p 14269:14269 \
-p 9411:9411 \
jaegertracing/all-in-one:1.60
这里启动了很多端口,详细的自查即可,这里两个比较重要的端口如下:
16686:服务前端端口,ip:port 可访问UI
6831:项目中连接端口
jaeger界面图【2】所示。
图 2
3.2 protoc编译器及语言插件安装
protobuf协议,是一种跨语言、跨平台的数据结构序列化和反序列化框架,类似于thrift协议
1 编写 .proto ⽂件,⽬的是为了定义结构对象(message)及属性内容。
2 使⽤ protoc 编译器编译 .proto ⽂件,⽣成⼀系列接⼝代码,存放在新⽣成头⽂件和源⽂件中。
3 依赖⽣成的接⼝,将编译⽣成的头⽂件包含进我们的代码中,实现对 .proto ⽂件中定义的字段进行设置和获取,和对 message 对象进行序列化和反序列化
安装
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
1
2
编写proto文件
syntax = "proto3";
option go_package = "./proto";
package helloworld;
service Greeter {
rpc SayHello (HelloReq) returns (HelloResp);
}
message HelloReq {
string name = 1;
}
message HelloResp {
string message = 1;
}
编译proto
protoc --go_out ../ helloword.proto
3.2 项目实现
项目架构是上层服务http,下层服务rpc,选用protobuf协议(跨语言、跨平台的数据结构序列化和反序列化框架,类似于thrift协议)作为就以简单的helloword为例实现。
核心插件jaeger、opentracing和otgrpc
在上层增加中间件并注册到engine上。
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
)
func Trace() gin.HandlerFunc {
return func(ctx *gin.Context) {
cfg := jaegercfg.Configuration{
ServiceName: "demo", //服务名称
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "xxxx:6831", //jaeger 服务器ip
},
}
tracer, closer, err := cfg.NewTracer()
if err != nil {
panic(err)
}
opentracing.SetGlobalTracer(tracer)
defer closer.Close()
startSpan := tracer.StartSpan(ctx.Request.URL.Path)
defer startSpan.Finish()
ctx.Set("tracer", tracer)
ctx.Set("parentSpan", startSpan)
ctx.Next()
}
}
// rpc调用设置globaltracer
consul := ServerConfig.Consul
conn, err := grpc.Dial(
fmt.Sprintf("consul://%s:%s/%s?wait=14s", consul.Host, consul.Port, ServerName),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())), //这里把全局tracer传下去
)
// 注册路由中间件
engine.Use(middleware.Trace())
启动项目访问就可以在jaeger上看到链路信息。
图 3
点击进入详情页查看信息,这时候大家发现一个问题,这里只有http层的调用信息,没有rpc层,这也是网上所有介绍的文档里没有说明白的一点。
图 4
真正原因是中间件代码里的tracer和parentSpan没有传递下去。
startSpan := tracer.StartSpan(ctx.Request.URL.Path)
defer startSpan.Finish()
ctx.Set("tracer", tracer)
ctx.Set("parentSpan", startSpan) // 虽然设置了,没有跟随rpc传下去
ctx.Next()
1
2
3
4
5
怎么传递呢?(这里是关键)
首先是在rpc服务发现时传递全局tracer
#引入传递tracer的包
go get github.com/lisw358/tts_otgrpc
1
2
源码里有一个地方别人做了改动,来实现tracer传递,改动如下:
//=============================自己改源码部分开始
//获取到父span和trace
//此处需要在ctx上有这个key来向下传递tracer和parentSpan
ginConetext := ctx.Value("ginContext")
switch ginConetext.(type) {
case *gin.Context:
//获取trace
if itrace, ok := ginConetext.(*gin.Context).Get("tracer"); ok {
tracer = itrace.(opentracing.Tracer)
}
//获取父span
if parentSpan, ok := ginConetext.(*gin.Context).Get("parentSpan"); ok {
parentCtx = parentSpan.(*jaeger.Span).Context()
}
}
//=============================自己改源码部分结束
因此全局定义一个ContextWapper,来增加ginContext的设置
然后在调用时处理一下context即可
req := &proto.HelloReq{}
req.Name = c.DefaultQuery("name", "guest")
//这里包装了一下context,来保证otgrpc包里能正常判断ginContext变量,来生成子span
res, _ := services.SayHello(global.ContextWrapper(c), req)
c.JSON(0, res.Message)
jaeger链路追踪结果如图【5】所示
图 5
本文完
参考文档
jaeger tracing document
https://chenquan.me/posts/tracing-system-analysis/
https://github.com/lisw358/tts_otgrpc
https://blog.51cto.com/u_15481067/11749036
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/cjqh_hao/article/details/141993511