go-gin-api 路由中间件 - Jaeger 链路追踪
概述
首先同步下项目概况:
上篇文章分享了,路由中间件 - jaeger 链路追踪(理论篇)。
这篇文章咱们分享:路由中间件 - jaeger 链路追踪(实战篇)。
说实话,这篇文章确实让大家久等了,主要是里面有一些技术点都是刚刚研究的,没有存货。
先看下咱们要实现的东西:
api 调用了 5 个服务,其中 4 个 grpc 服务,1 个 http 服务,服务与服务之间又相互调用:
speak 服务,又调用了 listen 服务 和 sing 服务。
read 服务,又调用了 listen 服务 和 sing 服务。
write 服务,又调用了 listen 服务 和 sing 服务。
咱们要实现的就是查看 api 调用的链路。
关于一些理论的东西,大家可以去看看上篇文章或查阅一些资料,这篇文章就是实现怎么用。
ok,开整。
jaeger 部署
咱们使用 all in one 的方式,进行本地部署。
下载地址:https://www.jaegertracing.io/download/
我的电脑是 macos 选择 -> binaries -> macos
下载后并解压,会发现以下文件:
example-hotrod
jaeger-agent
jaeger-all-in-one
jaeger-collector
jaeger-ingester
jaeger-query
进入到解压后的目录执行:
./jaeger-all-in-one
目测启动后,访问地址:
http://127.0.0.1:16686/
到这,jaeger 已经部署成功了。
准备测试服务
准备的五个测试服务如下:
听(listen)
端口:9901
通讯:grpc
说(speak)
端口:9902
通讯:grpc
读(read)
端口:9903
通讯:grpc
写(write)
端口:9904
通讯:grpc
唱(sing)
端口:9905
通讯:http
听、说、读、写、唱,想这几个服务的名称就花了好久 ~
我默认大家都会写 grpc 服务,如果不会写的,可以查看下我原来的文章《go grpc hello world》。
应用示例
实例化 tracer
func newjaegertracer(servicename string, jaegerhostport string) (opentracing.tracer, io.closer, error) {
cfg := &jaegerconfig.configuration {
sampler: &jaegerconfig.samplerconfig{
type : "const", //固定采样
param : 1, //1=全采样、0=不采样
},
reporter: &jaegerconfig.reporterconfig{
logspans : true,
localagenthostport : jaegerhostport,
},
servicename: servicename,
}
tracer, closer, err := cfg.newtracer(jaegerconfig.logger(jaeger.stdlogger))
if err != nil {
panic(fmt.sprintf("error: cannot init jaeger: %v\n", err))
}
opentracing.setglobaltracer(tracer)
return tracer, closer, err
}
http 注入
injecterr := jaeger.tracer.inject(span.context(), opentracing.httpheaders, opentracing.httpheaderscarrier(req.header))
if injecterr != nil {
log.fatalf("%s: couldn't inject headers", err)
}
http 拦截
spctx, err := opentracing.globaltracer().extract(opentracing.httpheaders, opentracing.httpheaderscarrier(c.request.header))
if err != nil {
parentspan = tracer.startspan(c.request.url.path)
defer parentspan.finish()
} else {
parentspan = opentracing.startspan(
c.request.url.path,
opentracing.childof(spctx),
opentracing.tag{key: string(ext.component), value: "http"},
ext.spankindrpcserver,
)
defer parentspan.finish()
}
grpc 注入
func clientinterceptor(tracer opentracing.tracer, spancontext opentracing.spancontext) grpc.unaryclientinterceptor {
return func(ctx context.context, method string,
req, reply interface{}, cc *grpc.clientconn,
invoker grpc.unaryinvoker, opts ...grpc.calloption) error {
span := opentracing.startspan(
"call grpc",
opentracing.childof(spancontext),
opentracing.tag{key: string(ext.component), value: "grpc"},
ext.spankindrpcclient,
)
defer span.finish()
md, ok := metadata.fromoutgoingcontext(ctx)
if !ok {
md = metadata.new(nil)
} else {
md = md.copy()
}
err := tracer.inject(span.context(), opentracing.textmap, mdreaderwriter{md})
if err != nil {
span.logfields(log.string("inject-error", err.error()))
}
newctx := metadata.newoutgoingcontext(ctx, md)
err = invoker(newctx, method, req, reply, cc, opts...)
if err != nil {
span.logfields(log.string("call-error", err.error()))
}
return err
}
}
grpc 拦截
func serverinterceptor(tracer opentracing.tracer) grpc.unaryserverinterceptor {
return func(ctx context.context,
req interface{},
info *grpc.unaryserverinfo,
handler grpc.unaryhandler) (resp interface{}, err error) {
md, ok := metadata.fromincomingcontext(ctx)
if !ok {
md = metadata.new(nil)
}
spancontext, err := tracer.extract(opentracing.textmap, mdreaderwriter{md})
if err != nil && err != opentracing.errspancontextnotfound {
grpclog.errorf("extract from metadata err: %v", err)
} else {
span := tracer.startspan(
info.fullmethod,
ext.rpcserveroption(spancontext),
opentracing.tag{key: string(ext.component), value: "grpc"},
ext.spankindrpcserver,
)
defer span.finish()
parentcontext = opentracing.contextwithspan(ctx, span)
}
return handler(parentcontext, req)
}
}
上面是一些核心的代码,涉及到的全部代码我都会上传到 github,供下载。
运行
启动服务
// 启动 listen 服务
cd listen && go run main.go
// 启动 speak 服务
cd speak && go run main.go
// 启动 read 服务
cd read && go run main.go
// 启动 write 服务
cd write && go run main.go
// 启动 sing 服务
cd sing && go run main.go
// 启动 go-gin-api 服务
cd go-gin-api && go run main.go
访问路由
http://127.0.0.1:9999/jaeger_test
效果
基本实现了,就到这吧。