当前位置: 首页 > 工具软件 > Jaeger > 使用案例 >

Jaeger

吕学
2023-12-01

介绍

jeager 是一个 分布式追踪工具,在微服务架构中运用尤为广泛,
在 微服务架构 中 通常有 几个上百 个 微服务,一个完整的业务流程 通常
由 多个微服务 来 协同完成, 即便是 相同的 业务流程, 不同的请求 所
经过的 微服务 也不尽相同,这就给 错误分析 和 性能瓶颈分析 带来了 大困难

分部署追踪 是 解决上述问题 的常见做法,分布式追踪 是一个比较大的话题,
所涉及的 规范api第三方工具 也比较多,目前 比较主流的是 opentracing 规范。
jaeger(Go开发) 和 zipkin(Java开发) 是目前流行的 开源实现。
jaeger 提供了 对 opentracing 的支持,确保了与其他工具的兼容性。

jaeger的 四大组件

  • Agent
    agent 的作用是 通过 udp 接收 追踪信息,并以批量的方式 发送给 collector。

  • Collector
    collector 负责接收 agent 发送的信息 并 进行处理, 在完成 验证、索引、转换 后 保存在 存储中。
    jaeger的存储 是可插拔的 组件。目前提供了 对 kafkaelasticsearch、cassandra 的支持。

  • Query
    query 对外提供 查询服务 和 用户界面。

  • Ingester
    负责从 kafka 中的 topic 读取数据,并写入到 其他 存储中。

这些组件可以 统一部署 也可以 分开部署

  • 统一部署
    适用于 数据量 较小的情况, 这种部署 使用的是 all-in-one 的镜像,
    docker pull joinsunsoft/etcdv3-browser:1.0.0
  • 分开部署
    每个组件 独立运行,适用于 数据量 较大的情况

Opentracing

Opentracing是 分布式链路追踪 的一种行为规范标准,该项目隶属于CNCF(云原生计算基金会)。
与一般的规范标准不同,它不是传输协议,消息格式层面 上的规范标准,而是一种语言层面上的API标准
以Go语言为例,只要 某链路追踪系统 实现了Opentracing规定的接口(interface),那么就可以说该应用符合Opentracing标准。

下面介绍 Opentracing 的 两个核心概念:

Span

Span是 一条追踪链路 中的 基本组成单位,一个span表示一个独立的工作单元,
比如可以表示一次函数调用,一次http请求等等。
span会记录如下基本信息:

  • 操作名称(operation name)
  • 开始时间
  • 执行时长
  • Tags
  • Logs
  • SpanContext
  • References

Tags

Tags 以 键值对 的形式 保存 用户自定义标签,主要用于 链路追踪结果 的查询筛选。
其中key值必须为字符串,value必须是字符串,布尔型或者数值型。
span 中的 tag 仅自己可见不会随着 SpanContext传递给后续span

span.SetTag("name","tom")

Logs

logs 也是 键值对 形式 存储信息。与tags不同的是,logs还会记录写入logs的时间
因此logs主要用于记录 某些事件发生 的时间。
logs的key值同样必须为字符串,但对value类型则没有限制。

span.LogFields(
	log.String("event_type", "unkonw error happen"),
	log.Int("time_out", 100),
)

SpanContext

SpanContext携带着一些用于 跨服务通信的(跨进程) 的 数据,主要包含:span_id, trace_id。

  • Baggage Items
    Baggage Items 为 整条追踪链 保存跨服务(跨进程)的 用户自定义数据。
    与tags类似,也是K/V键值对。与tags不同的是:
  1. 其key跟value都只能是字符串格式
  2. Baggage items 其会随着SpanContext传递给后续所有的子span。
    因此要小心谨慎的使用baggage items,因为在所有的span中传递这些K,V会带来不小的网络和CPU开销

References

Opentracing定义了 两种引用关系 : ChildOfFollowFrom

  • ChildOf:

父span 的执行 依赖 子span的执行结果。

  • FollowFrom:

父span的执 不依赖 子span执行结果时。FollowFrom常用于异步调用的表示。

Trace

Trace表示 一个完整的追踪链路,trace由一个或多个span组成,是span的有向无环图。

        [Span 1]
            |
     +------+------+
     |             |
 [Span 2]      [Span 3] 
     |             |
 [Span 4]      +---+-------+
               |           |
           [Span 5]    [Span 6] >>> [Span 7] 
           
3 是 1 的 `ChildOf`
7 是 6 的 `FollowsFrom`

Go操作

  • 环境准备
docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 9411:9411 jaegertracing/all-in-one:latest
  • Demo
package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"time"

	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/log"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
)

// 将jaeger tracer设置为全局tracer
func initJaeger(serviceName string) io.Closer {
	cfg := jaegercfg.Configuration{
		// 将采样频率设置为1,每一个span都记录,方便查看测试结果
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans: true,
			// 将span发往 jaeger-collector 的服务地址
			CollectorEndpoint: "http://localhost:14268/api/traces",
		},
	}
	closer, err := cfg.InitGlobalTracer(serviceName, jaegercfg.Logger(jaeger.StdLogger))
	if err != nil {
		panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
	}
	return closer
}

func main() {
	closer := initJaeger("in-process")
	defer closer.Close()

	// 获取jaeger tracer
	t := opentracing.GlobalTracer()

	// 创建root span
	spanRoot := t.StartSpan("in-process-service")
	// main执行完结束这个span
	defer spanRoot.Finish()

	// 将span传递给Foo
	c := opentracing.ContextWithSpan(context.Background(), spanRoot)

	// 后继传递,将context传递给Foo
	Foo(c)
}

func Foo(c context.Context) {
	// 开始一个span, 设置span的operation_name=Foo
	span, c := opentracing.StartSpanFromContext(ctx, "Foo")
	defer span.Finish()

	// 后继传递,将context传递给Bar
	Wtt(c)

	// 模拟执行耗时
	time.Sleep(1 * time.Second)

	// 后继传递,将context传递给Bar
	Bar(c)

}

func Bar(c context.Context) {
	// 开始一个span,设置span的 operation_name = Bar
	span, _ := opentracing.StartSpanFromContext(c, "Bar")
	defer span.Finish()
	// 模拟执行耗时
	time.Sleep(2 * time.Second)

	// 假设Bar发生了某些错误
	err := errors.New("something wrong")
	// 记录错误信息
	span.LogFields(
		log.String("event", "error"),
		log.String("message", err.Error()),
	)
	span.SetTag("error", true)
}

// 内部代码 同 Bar
func Wtt(c context.Context) {
	// 开始一个span,设置span的 operation_name = Bar
	span, _ := opentracing.StartSpanFromContext(c, "Wtt")
	defer span.Finish()
	// 模拟执行耗时
	time.Sleep(2 * time.Second)

	// 假设Bar发生了某些错误
	err := errors.New("something wrong")
	// 记录错误信息
	span.LogFields(
		log.String("event", "error"),
		log.String("message", err.Error()),
	)
	span.SetTag("error", true)
}
 类似资料: