对OpenTelemetry的概念有初步了解后,我们接着以Jaeger为例来演示如何在程序中使用实现链路追踪。
Jaeger是Uber开源的分布式追踪系统,是支持OpenTelemetry的系统之一,也是CNCF项目。本篇将使用Jaeger来演示如何在系统中引入分布式追踪。以下是Opentracing+Jaeger的架构图,针对于使用OpenTelemetry也是如此。
Jaeger官方提供了all-in-one的docker镜像,可以基于此进行一键部署。
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.55
也可以使用docker compose来启动。
version: '3.1'
services:
db:
image: jaegertracing/all-in-one
restart: always
environment:
COLLECTOR_ZIPKIN_HTTP_PORT: 9411
ports:
- 5775:5775/udp
- 6831:6831/udp
- 6832:6832/udp
- 5778:5778
- 16686:16686
- 14268:14268
- 9411:9411
甚至还可以通过下载二进制文件直接运行。
下载地址:Jaeger Binaries
启动参数:
$ jaeger-all-in-one --collector.zipkin.http-port=9411
启动之后就可以在http://localhost:16686
看到Jaeger的UI界面了。
大致步骤如下:
func main() {
// 解析命令行参数
if len(os.Args) != 2 {
panic("ERROR: Expecting one argument")
}
// 1.初始化 tracer
tracer, closer := config.NewTracer("hello")
defer closer.Close()
// 2.开始新的 Span (注意:必须要调用 Finish()方法span才会上传到后端)
span := tracer.StartSpan("say-hello")
defer span.Finish()
helloTo := os.Args[1]
helloStr := fmt.Sprintf("Hello, %s!", helloTo)
// 3.通过tag、log记录注释信息
// LogFields 和 LogKV底层是调用的同一个方法
span.SetTag("hello-to", helloTo)
span.LogFields(
log.String("event", "string-format"),
log.String("value", helloStr),
)
span.LogKV("event", "println")
println(helloStr)
}
func NewTracer(service string) (opentracing.Tracer, io.Closer) {
// 参数详解 https://www.jaegertracing.io/docs/1.20/sampling/
cfg := jaegerConfig.Configuration{
ServiceName: service,
// 采样配置
Sampler: &jaegerConfig.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegerConfig.ReporterConfig{
LogSpans: true,
CollectorEndpoint: "http://localhost:14268/api/traces", // 将span发往jaeger-collector的服务地址
},
}
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
}
运行上述例子后就可以在Jaeger UI界面看到对应的链路信息了。
opentracing.ChildOf(rootSpan.Context())
保留span之间的因果关系。span是链路追踪里的最小组成单元,为了保留各个功能之间的因果关系,必须在各个方法之间传递span并且新建span时指定opentracing.ChildOf(rootSpan.Context())
,否则新建的span会是独立的,无法构成一个完整的trace。
比如方法A调用了B、C、D,那么就需要将方法A中的span传递到方法BCD中。
childSpan := rootSpan.Tracer().StartSpan(
"formatString",
opentracing.ChildOf(rootSpan.Context()),
)
通过opentracing.ChildOf(rootSpan.Context())
建立两个span之间的引用关系,如果不指定则会创建一个新的span(UI中查看的时候就是一个新的trace)。
将前面的例子稍微修改一下,将formatString和printHello提成单独的方法,并新增span参数。
func main() {
// 解析命令行参数
if len(os.Args) != 2 {
panic("ERROR: Expecting one argument")
}
// 1.初始化 tracer
tracer, closer := config.NewTracer("hello")
defer closer.Close()
// 2.开始新的 Span (注意:必须要调用 Finish()方法span才会上传到后端)
span := tracer.StartSpan("say-hello")
defer span.Finish()
helloTo := os.Args[1]
helloStr := formatString(span, helloTo)
printHello(span, helloStr)
}
func formatString(span opentracing.Span, helloTo string) string {
childSpan := span.Tracer().StartSpan(
"formatString",
opentracing.ChildOf(span.Context()),
)
defer childSpan.Finish()
return fmt.Sprintf("Hello, %s!", helloTo)
}
func printHello(span opentracing.Span, helloStr string) {
childSpan := span.Tracer().StartSpan(
"printHello",
opentracing.ChildOf(span.Context()),
)
defer childSpan.Finish()
println(helloStr)
}
运行之后可以清楚的在UI界面中看到say-hello
由formatString
和printHello
两个功能组成。
前面虽然保留了span的因果关系,但是需要在各个方法中传递span。这可能会污染整个程序,我们可以借助Go语言中的context.Context
对象来进行传递。
实例代码如下:
ctx := context.Background()
ctx = opentracing.ContextWithSpan(ctx, span)
helloStr := formatString(ctx, helloTo)
printHello(ctx, helloStr)
func formatString(ctx context.Context, helloTo string) string {
span, _ := opentracing.StartSpanFromContext(ctx, "formatString")
defer span.Finish()
...
}
func printHello(ctx context.Context, helloStr string) {
span, _ := opentracing.StartSpanFromContext(ctx, "printHello")
defer span.Finish()
...
}
opentracing.StartSpanFromContext()
返回的第二个参数是子ctx
,如果需要的话可以将该子ctx继续往下传递,而不是传递父ctx。
需要注意的是opentracing.StartSpanFromContext()
默认使用GlobalTracer
来开始一个新的span,所以使用之前需要设置GlobalTracer。
opentracing.SetGlobalTracer(tracer)
如果您喜欢我的文章,请点击下面按钮随意打赏,您的支持是我最大的动力。
最新评论