37DATA

分布式链路追踪系统-Jaeger实践

导语

Jaeger是用Go写的一款软件,刚好我们最近也在转go开发,看到了网上一些推文的介绍,所以就做一波实践,并分享给大家。

 什么是Jaeger?

Jaeger 是用于追踪分布式服务之间事务的开源软件,它为微服务场景而生。作用链路追踪系统,顾名思义Jaeger的最主要的作用当然是帮我们的开发人员在各种分布式的服务中更方便的定位追踪到问题的位置,分析多个服务的调用过程,图形化服务调用轨迹,是一款诊断性能问题、分析系统故障的工具。
官方给出的特性介绍:
1、分布式上下文传递
2、分布式事务监控
3、根本原因分析
4、服务依赖分析
5、性能、延迟优化

Jaeger的工作原理

Jaeger 内含多个组件,这些组件可以协同工作,一起收集、存储和可视化跨度与迹线。

下面是Jaeger每个组件的工作原理:
  • Jaeger-client 包括含用于分布式跟踪的 OpenTracing API 的特定语言实施。Jaeger客户端代码库,便于不同语言的项目来介入到Jaeger中,当我们的应用程序装载上之后,client会负责收集并发送数据到Agent。
  • Jaeger-agent 是一个网络守护进程,可侦听通过用户数据报协议发送的跨度。该代理应与所检测的应用放置在同一主机上。这通常是通过 Kubernetes 等容器环境中的 sidecar 实现的。
  • Jaeger-collector 从agent收集traces信息,并通过处理管道处理他们,再写入后端存储(backends)。当前的collector工作主要是管理trace,建立索引,执行相关转换,并最终存储它们。
  • Jaeger 控制台是用于直观查看分布式跟踪数据的用户界面。Query查询是一种从存储中检索trace,并提供UI以显示它们的服务。上图中就展示了一次Trace的数据流向,作为一次系统作用的数据传播/执行图,即可以在Jaeger UI上展示出来
先解释一下什么是Spans:

表示一个逻辑工作单元,包含有操作名称、起始时间以及操作耗时。span之间可以存在嵌套和并排关系,span之间也有顺序。

下图是官方提供的Jaeger架构图,其中最上面的一行表示Jaeger支持的语言类型,客户端的span被送到jaeger-agent(通过UDP协议),然后被推送到jaeger-collector,数据被jaeger-collector写入数据库(DB),然后用户通过浏览器访问jaeger-query,就能看到详细的调用链追踪结果了。

Image

安装实践

1、使用docker安装是最简单的,只需要一行指令就能完成安装

git clone [email protected]:jaegertracing/jaeger.git jaeger cd jaeger/examples/hotrod go run ./main.go alldocker run -d --name=jaeger -p6831:6831/udp -p16686:16686 jaegertracing/all-in-one:latest
2、Jaeger有提供可视化Web UI,安装后我们可以浏览器访问http://localhost:16686/ 进入Web UI
Image
3、建立一个客户端

这个应用是Jaeger仓库中的一个示例,我们clone下来并启动

git clone [email protected]:jaegertracing/jaeger.git jaeger cd jaeger/examples/hotrod go run ./main.go allgit clone [email protected]:jaegertracing/jaeger.git jaegercd jaeger/examples/hotrodgo run ./main.go all
git地址:https://github.com/jaegertracing/jaeger/tree/main

4、hotrod启动后访问webUI:http://127.0.0.1:8080 ,这是官方提供的示例客户端,我们可以通过它来进行实践

Image

可以看到左上角有个客户端id,每次刷新都会生成一个新的;而下面的四个按钮分别代表四个客户,点击一个按钮就会给他下单一辆车并送过去,就是一个下单请求,响应数据是车牌号以及预计到达时间。

5、点击按钮下单

Image

Image

下单后显示的分别是车牌号,预计到达时间,请求序列号,请求耗时。我们点多几下。

6、回到Jaeper Web UI,查看Traces

Image

点击每一个trace里面,能看到详情,由于示例比较简单,看到的链路信息不会很多,一个 trace 代表了一个事务或者流程在(分布式)系统中的执行过程

Image

举个例子:调用一次查询用户信息的请求,这里分几步:
  1. 请求到达路由服务,路由服务根据路径调用handler

  2. handler内部调用service服务

  3. service根据路由调用其service-handler

  4. service-handler内调用mysql
这里就有四个耗时阶段,每个阶段都要耗时,将每个阶段称作是一个span,你会发现上一个阶段会包含下面的所有阶段,而每个span都有一些元信息:
  1. span-id

  2. 操作名

  3. 开始时间

  4. 结束时间

  5. 耗时

  6. tag: 用户自定义标签便于查询过滤和理解数据

  7. log: 记录 span 内特定时间或事件的日志信息,以及应用程序本身的其他调试或信息输出

  8. span context: 跨越进程边界,传递到子级 span 的状,常在追踪示意图中创建上下文时使用

我们再简单用Go来写一个简单的Demo:

1、先写一个初始化jaeger的函数
func initJaeger(service string) (opentracing.Tracer, io.Closer) {    cfg := &config.Configuration{        Sampler:&config.SamplerConfig{            Type:     "const",            Param:1,        },        Reporter: &config.ReporterConfig{            LogSpans:            true,            LocalAgentHostPort:  "127.0.0.1:6831",        },    }    tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))    if err != nil {        panic(fmt.Sprintf("Error: connot init Jaeger: %v\n", err))    }    return tracer, closer}

2、然后我们在main函数中创建调用InitJaeger,并创建一个root span,调用两个函数,分别表示调用两个分布式服务。我们用ContextWithSpan来创建一个新的ctx,将span的信息与context关联,传到TestDemo中时,需要创建一个子span,父span是ctx中的span。

func TestDemo(req string, ctx context.Context) (reply string) {// 1. 创建span    span, _ := opentracing.StartSpanFromContext(ctx, "span_testdemo")defer func() {// 4. 接口调用完,在tag中设置request和reply        span.SetTag("request", req)        span.SetTag("reply", reply)        span.Finish()    }()
println(req)//2. 模拟耗时 time.Sleep(time.Second/2)//3. 返回reply reply = "TestDemoReply"return}
// TestDemo2, 和上面TestDemo 逻辑代码一样func TestDemo2(req string, ctx context.Context) (reply string) { span, _ := opentracing.StartSpanFromContext(ctx, "span_testdemo2")defer func() { span.SetTag("request", req) span.SetTag("reply", reply) span.Finish() }()
println(req) time.Sleep(time.Second/2) reply = "TestDemo2Reply"return}
func main() { tracer, closer := initJaeger("jager-test-demo")defer closer.Close() opentracing.SetGlobalTracer(tracer)
span := tracer.StartSpan("span_root") ctx := opentracing.ContextWithSpan(context.Background(), span) r1 := TestDemo("Hello TestDemo", ctx) r2 := TestDemo2("Hello TestDemo2", ctx) fmt.Println(r1, r2) span.Finish()}

运行demo会打印出来:

21:57:30 debug logging disabled
21:57:30 Initializing logging reporter
21:57:30 debug logging disabled
Hello TestDemo
21:57:30 Reporting span 2163520004cced2a:4155a263b5147904:2163520004cced2a:1
Hello TestDemo2
21:57:31 Reporting span 2163520004cced2a:01928bf482621c17:2163520004cced2a:1
TestDemoReply TestDemo2Reply
21:57:31 Reporting span 2163520004cced2a:2163520004cced2a:0000000000000000:1

然后在去web UI上刷新查看,就会出现trace记录

 总结

根据上面的实践,我们就能方便定位到每一个trace,甚至每一个span出现问题的位置,提高我们的开发效率。当然jaeger还有很多其他的功能,例如接口出现问题的时候,对比2次相同的请求其trace的不同之处,进而缩小范围找到具体不同的span出现在哪。还有更多就不一一列举了,感兴趣的小伙伴可以实践一下,一起探讨。