分布式追踪可观测平台(如 Jaeger)对于架构为微服务的现代软件应用程序至关重要。Jaeger 可以映射分布式系统中的请求流和数据流。这些请求可能会调用多个服务,而这些服务可能会带来各自的延迟或错误。Jaeger 将这些不同组件之间的点连接起来,帮助识别性能瓶颈、排除故障并提高整体应用程序的可靠性。Jaeger是100%开源、云原生、可无限扩展的。

什么是Jaeger

Jaeger 是一个分布式追踪系统。Jaeger的灵感来自 DapperOpenZipkin,是一个由 Uber 创建并捐赠给 云原生计算基金会(CNCF) 的分布式跟踪平台。它可以用于监控基于微服务的分布式系统:

  • 分布式上下文传递
  • 分布式事务监听
  • 根因分析
  • 服务依赖性分析
  • 性能/延迟优化

Jaeger架构

graph TD
    SDK["OpenTelemetry SDK"] --> |HTTP or gRPC| COLLECTOR
    COLLECTOR["Jaeger Collector"] --> STORE[Storage]
    COLLECTOR --> |gRPC| PLUGIN[Storage Plugin]
    COLLECTOR --> |gRPC/sampling| SDK
    PLUGIN --> STORE
    QUERY[Jaeger Query Service] --> STORE
    QUERY --> |gRPC| PLUGIN
    UI[Jaeger UI] --> |HTTP| QUERY
    subgraph Application Host
        subgraph User Application
            SDK
        end
    end
  

特性

高可扩展性

Jaeger后端设计为无单点故障,并可根据业务需求进行扩展。例如,通常 Uber 的任何一个 Jaeger 实例每天都要处理数十亿个 span。

与OpenTelemetry的关系

Jaeger 项目和 OpenTelemetry 项目有着不同的目标。OpenTelemetry 的目标是提供多种语言的应用程序接口(API)和 SDK,允许应用程序将各种遥测数据输出到任意数量的度量和跟踪后端。Jaeger 项目主要是跟踪后端,它接收跟踪遥测数据,并对数据进行处理、汇总、数据挖掘和可视化。如需了解更多信息,请参阅博客 《Jaeger and OpenTelemetry》

Jaeger 最初是为支持 OpenTracing 标准而设计的,它实现了一套客户端工具——Jaeger SDK,但是目前 Jaeger 项目推荐使用 OpenTelemetry SDK 作为仪器(Jaeger SDK已废弃)。

Jaeger Collector 现在已能够接收 OTLP 格式的数据。

支持多种存储后端

Jaeger 可以与多种存储后端配合使用,它原生支持两种流行的开源 NoSQL 数据库作为跟踪存储后端:CassandraElasticsearch,此外它通过 gRPC API 与 ClickHouse 集成。

现代化的Web UI

Jaeger Web UI 使用流行的开源框架(React)实现。 v1.0 版支持高效处理大量数据,并显示数以万计 span 的 trace。

云原生部署

Jaeger 后端以 Docker 镜像集的形式发布。二进制文件支持多种配置方法,包括命令行选项、环境变量和多种格式的配置文件(yaml、toml 等)。

在生产 Kubernetes 集群中部署 Jaeger 的推荐方式是通过 Jaeger Operator。Jaeger Operator提供了一个 CLI,用于从 Jaeger CR 生成 Kubernetes manifests 。

Jaeger 生态系统还提供了 Helm chart 部署 Jaeger 的替代方式。

可观测性

默认情况下,所有 Jaeger 后端组件都公开 Prometheus 指标(也支持其他指标后端)。使用结构化日志库 zap 将日志写入标准输出。

使用 Docker 在本地运行 Jaeger

Jaeger 提供了一个随时可用的 all-in-one Docker 镜像,其中包含 Jaeger UIjaeger-collectorjaeger-queryjaeger-agent,以及一个内存存储组件。

在你的电脑上执行以下命令运行 Jaeger:

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

容器公开以下端口:

Port Protocol Component Function
6831 UDP agent accept jaeger.thrift over Thrift-compact protocol (used by most SDKs)
6832 UDP agent accept jaeger.thrift over Thrift-binary protocol (used by Node.js SDK)
5775 UDP agent (deprecated) accept zipkin.thrift over compact Thrift protocol (used by legacy clients only)
5778 HTTP agent serve configs (sampling, etc.)
16686 HTTP query serve frontend
4317 HTTP collector accept OpenTelemetry Protocol (OTLP) over gRPC
4318 HTTP collector accept OpenTelemetry Protocol (OTLP) over HTTP
14268 HTTP collector accept jaeger.thrift directly from clients
14250 HTTP collector accept model.proto
9411 HTTP collector Zipkin compatible endpoint (optional)

容器启动后,使用浏览器打开 http://localhost:16686 即可访问 Jaeger UI。

jaeger-ui

Jaeger Tracing Go示例

Jaeger Go示例

下面是一个批量创建 span 并将 trace 数据发送到 Jaeger 的 Go 语言示例。

package main

import (
	"context"
	"fmt"
	"math/rand"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/sdk/resource"
	traceSDK "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
	"go.opentelemetry.io/otel/trace"
)

// jaeger trace Go demo by Q1mi

const (
	serviceName    = "Go-Jaeger-Demo"
	jaegerEndpoint = "127.0.0.1:4318"
)

// setupTracer 设置Tracer
func setupTracer(ctx context.Context) (func(context.Context) error, error) {
	tracerProvider, err := newJaegerTraceProvider(ctx)
	if err != nil {
		return nil, err
	}
	otel.SetTracerProvider(tracerProvider)
	return tracerProvider.Shutdown, nil
}

// newJaegerTraceProvider 创建一个 Jaeger Trace Provider
func newJaegerTraceProvider(ctx context.Context) (*traceSDK.TracerProvider, error) {
	// 创建一个使用 HTTP 协议连接本机Jaeger的 Exporter
	exp, err := otlptracehttp.New(ctx,
		otlptracehttp.WithEndpoint(jaegerEndpoint),
		otlptracehttp.WithInsecure())
	if err != nil {
		return nil, err
	}
	res, err := resource.New(ctx, resource.WithAttributes(semconv.ServiceName(serviceName)))
	if err != nil {
		return nil, err
	}
	traceProvider := traceSDK.NewTracerProvider(
		traceSDK.WithResource(res),
		traceSDK.WithSampler(traceSDK.AlwaysSample()), // 采样
		traceSDK.WithBatcher(exp, traceSDK.WithBatchTimeout(time.Second)),
	)
	return traceProvider, nil
}

// testTracer 编写一个批量创建span的tracer
// trace 和 span
func testTracer(ctx context.Context) {
	tracer := otel.Tracer("test-tracer")
	baseAttrs := []attribute.KeyValue{
		attribute.String("domain", "liwenzhou.com"),
		attribute.Bool("plagiarize", false),
		attribute.Int("code", 7),
	}

	// 开启span
	ctx, span := tracer.Start(ctx, "parent-span", trace.WithAttributes(baseAttrs...))
	// 结束span
	defer span.End()
	// 使用for循环创建多个子span,方便查看效果
	for i := range 10 { // Go1.22+
		// 传入父ctx,开启子span
		_, iSpan := tracer.Start(ctx, fmt.Sprintf("span-%d", i))
		// 随机sleep,模拟子span中耗时的操作
		time.Sleep(time.Duration(rand.Int63n(100)) * time.Millisecond)
		// 子span结束
		iSpan.End()
	}
	fmt.Println("done!")
}

func main() {
	ctx := context.Background()
	shutdown, err := setupTracer(ctx)
	if err != nil {
		panic(err)
	}
	defer func() {
		_ = shutdown(ctx)
	}()

	// 批量创建span并上报至Jaeger
	testTracer(ctx)
}

将上述代码运行。

$ go run .
done!

Jaeger UI 使用

你可以使用Jaeger UI 左侧的搜索面板来搜索具有特定属性的 trace: 它们来自哪个服务、进行了什么操作、包含在跟踪中的特定标记(例如,HTTP状态码)、过去需要查找多长时间以及结果数量限制。

Jaeger UI Search

当你输入了搜索条件后,点击"Find Traces"按钮后,即可看到类似下面的搜索结果。

JaegerUI search results

由于我运行了两次程序,所以页面上会有两条 trace 记录,可以点击其中一条记录查看详情。

JaegerUI trace detail

在这个页面,你可以找到关于执行时间、都发出了哪些调用、调用的持续时间以及携带的特定属性等信息。

总结

随着 OpenTelemetry 的稳步发展,Jaeger 更倾向于遵循 OpenTelemetry 规范,并把精力集中在构建跟踪后端、可视化工具和数据挖掘技术上。作为普通开发者,使用 OpenTelemetry SDK + Jaeger 将是分布式追踪的理想方案。

参考链接

https://www.jaegertracing.io/

https://medium.com/jaegertracing/jaeger-tracing-a-friendly-guide-for-beginners-7b53a4a568ca

其他常用库的OTel相关内容


扫码关注微信公众号