go-zero中使用jaeger链路追踪
链路追踪分两块:
(1)框架内置的,通过配置实现,一般都在api、rpc等中间件中,粗浅理解是请求级;
(2)自定义的,可以定义到函数里,粗浅理解可以自己写代码级的,也就是可以跟踪每个函数方法的执行时间;
本文参考资料:gozero官方公众号:https://mp.weixin.qq.com/s/xmSar4aG_HVuPvAi6auQOQ
1、准备工作
安装jaeger、es等环境,这里通过docker-compose:
注意:
(1)jaeger用的当前最新版本,但es选了老的7.13的版本,用当前最新的8.8会因为标签的问题连不上,暂未解决)
(2)需要设定网络组,我这里名字叫dandan_net
jaeger: container_name: jaeger image: jaegertracing/all-in-one:1.46.0 environment: - TZ=Asia/Shanghai - SPAN_STORAGE_TYPE=elasticsearch - ES_SERVER_URLS=http://elasticsearch:9200 - LOG_LEVEL=debug privileged: true ports: - "6831:6831/udp" - "6832:6832/udp" - "5778:5778" - "16686:16686" - "4317:4317" - "4318:4318" - "14250:14250" - "14268:14268" - "14269:14269" - "9411:9411" restart: always networks: - dandan_net elasticsearch: container_name: elasticsearch #image: elasticsearch:8.8.0 image: elasticsearch:7.13.1 environment: - TZ=Asia/Shanghai - discovery.type=single-node - "ES_JAVA_OPTS=-Xms512m -Xmx512m" #以下配置是8.x版本用的,但是jaeger连不上,放弃用7了 #- node.name=es #- cluster.name=elasticsearch #- bootstrap.memory_lock=true #- xpack.security.enabled=false #- xpack.security.http.ssl.enabled=false #- xpack.security.transport.ssl.enabled=false privileged: true ports: - "9200:9200" - "9300:9300" restart: always networks: - dandan_net networks: dandan_net: driver: bridge ipam: config: - subnet: 172.20.0.0/16
安装完毕后,成功启动,访问:http://localhost:16686/ 能成功打开jaeger-ui页面即可;
2、配置实现
以user-api服务为例,在user-api服务的etc/user-api.yaml配置文件中,添加配置:(应该只需要改Name名字就行,显示在jaeger-uie页面上的)
Telemetry: Name: user-api Endpoint: http://localhost:14268/api/traces Sampler: 1.0 Batcher: jaeger
同理,在其他api服务 或 rpc服务的etc配置文件中,添加类似的配置即可。
启动服务后,请求一次服务,然后到jaeger-ui里,查询一下,能看到请求信息即可,这样就能实现 api1 -> rpc2->rpc3 这样的服务层级的链路了;
3、自定义实现
1、首先在logic里,包需要有这几个
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace"
2、在logica方法里,获取api进来的span(应该是为了链路挂在api层级下)
//感觉是实例化一个trace,暂不确定多个情况下是否要重复创建 tracer := otel.GetTracerProvider().Tracer(trace.TraceName) //通过logic的ctx,让这个span挂到api层级下,其中oteltrace.SpanKindProducer只是一个tag标签,换其他的消费者、内部啥的好像都没事,会显示看的 //第一个参数sanCtx可用于继续创建下级span spanCtx, span := tracer.Start(l.ctx, "testParent1", oteltrace.WithSpanKind(oteltrace.SpanKindProducer)) //似乎这样就开始计时了,在结束计时的地方 // --------然后就可以写你的业务了-------- span.End() //结束计时 ,当然,如果一个函数执行到结束,可以直接defer span.End()
3、如果想要添加子链路,实现嵌套,已上面这个为例,只要用上面产生的spanCtx来粗昂见即可
//同理疑问,暂不确定这里有没有必要定义一个tracer2 tracer2 := otel.GetTracerProvider().Tracer(trace.TraceName) //这里start方法里,使用上面的ctx,就能实现嵌套了 _, span2 := tracer2.Start(spanCtx, "testChild2", oteltrace.WithSpanKind(oteltrace.SpanKindConsumer)) time.Sleep(500 * time.Microsecond) span2.End()
4、上面的子链路是内部直接用ctx的,可能某些场景需要传递(暂时也没get到啥场景要这么用,因为这里又个大问题,l.ctx也是要传递的)
//以第2部产生的spanCtx为例 carrier := &propagation.HeaderCarrier{} otel.GetTextMapPropagator().Inject(spanCtx, carrier) //这样ctx就放进carrier了,然后可以转成json传递,在其他地方可以接收这个json后,转回成wireContext //同时这里有大问号:l.ctx这个上下文如果也要的话,那转不转json没都必要了,直接spanCtx作为参数传递不就好了? wireContext := otel.GetTextMapPropagator().Extract(l.ctx, carrier) tracer3 := otel.GetTracerProvider().Tracer(trace.TraceName) _, span3 := tracer3.Start(wireContext, "testChild3", oteltrace.WithSpanKind(oteltrace.SpanKindConsumer)) time.Sleep(200 * time.Microsecond) span3.End()
5、最后通过这种方法,就可以实现一些嵌套的效果