0%

分布式链路追踪相关概念以及技术选型

分布式链路追踪相关概念以及技术选型

0x00.Distributed Tracing(分布式链路追踪)介绍

a.什么是分布式链路追踪

分布式链路追踪是一种在请求通过分布式云环境传播时观察请求的方法。分布式跟踪通过使用唯一标识符标记交互来跟踪交互。该标识符在事务与微服务、容器和基础设施交互时与事务保持一致。唯一标识符提供了对用户体验的实时可见性,从堆栈的顶部到应用程序层和下面的基础设施

对于可观测性 有三大指标:Logging、Metrics 和 Tracing

  • Logging:用于记录离散的事件,包含程序执行到某一点或某一阶段的详细信息。
  • Metrics:可聚合的数据,且通常是固定类型的时序数据,包括 Counter、Gauge、Histogram 等。
  • Tracing:记录单个请求的处理流程,其中包括服务调用和处理时长等信息。

上述几种指标 都有典型的应用

  • Logging:ELK
  • Metrics:Prometheus
  • Tracing:Jaeger、Zipkin

b.分布式链路追踪标准

业界较为通用的链路追踪标准有三种

  • OpenTracing:OpenTracing是一种应用程序级追踪规范,提供了一套API和数据模型,使得开发人员可以在系统中添加分布式追踪功能。它可以让开发人员在不同的服务之间添加追踪信息,以便了解消息流的情况,从而更好地分析性能瓶颈和研究其他问题。
  • OpenCensus:OpenCensus是一个开源、跨平台、跨语言的分布式跟踪和度量库。它由Google和Census.io共同开发。OpenCensus可以收集和传输跟踪、度量和日志数据,并提供了方便的API和工具来展示和分析这些数据。OpenCensus的目标是使跨平台和跨语言的监控变得更加简单。
  • OpenTelemetry:OpenTelemetry是一个可移植的、语言无关的分布式追踪工具包,可以用于监测分布式系统。它是OpenTracing和OpenCensus的继任者,并提供了一种集成方法,以便开发人员和运营人员可以轻松地捕获、分析和存储跨越多个系统的深度数据。OpenTelemetry还支持多种编程语言和后端。

OpenCensus和OpenTelemetry基于更先进的技术和更全面的功能,对于分布式追踪和性能监测更加有优势。建议在选择时,根据实际应用场景和需求,综合考虑现有的技术栈和人员素质等方面,选择最适合的开源分布式链路追踪系统。 #### c.OpenTracing介绍

​ OpenTracing是一种供应用程序开发人员使用的开放标准,用于在分布式系统中跟踪和监视请求的传播路径和性能。它提供了一个简单、一致的API和工具集,使开发人员能够在应用程序中插入跟踪代码,并收集关于请求的跟踪数据。

其中 OpenTracing有几个关键的组件,它们各自承担不同的功能,并通过协作来实现跟踪数据的收集、存储和可视化。

  1. Tracer(跟踪器):Tracer是OpenTracing的核心组件,用于创建Span、管理跟踪数据,并将数据发送到存储后端。它负责生成唯一的跟踪ID,记录Span的开始和结束时间,并收集Span的标签和日志信息。Tracer是应用程序与跟踪系统之间的接口,开发人员使用Tracer来仪器化应用程序代码。

  2. Span(跨度):Span是OpenTracing的基本单元,表示分布式系统中的一个操作或事件。Span包含了起始时间、结束时间、标签(Tag)、日志和其他元数据。Spans可以形成跟踪树,通过父子关系建立起操作之间的层次结构。Span记录了操作的持续时间、发生的事件以及与其他Spans的关系。

    span跟踪树:

  3. Context(上下文):Context是一个可传递的上下文对象,包含了跨越多个Span的共享信息。它用于在不同的服务之间传递Span和相关的上下文数据,确保Span能够正确关联。Context允许跟踪ID和父Span ID在分布式系统中正确传播,确保操作的连续性和一致性。

  4. Reporter(报告器):Reporter负责将Span数据发送到跟踪存储后端。它可以将Span数据发送到日志、消息队列或存储系统等。Reporter收集应用程序生成的Spans,并将它们发送到Collector进行进一步处理和存储。

  5. Collector(收集器):Collector接收来自Reporter的Span数据,并进行处理和存储。它负责将Span数据存储在后端存储系统中,以供进一步分析、查询和可视化。Collector可以将Span数据存储在数据库、分布式文件系统或云存储中。

这些组件之间的协作关系如下:

  1. 应用程序代码通过Tracer进行仪器化,插入Span的创建和结束代码,以及相应的标签和日志信息。这样,应用程序生成的Span数据就会被Tracer捕获和记录。
  2. Tracer将生成的Span数据传递给Reporter。Reporter负责将Span数据发送到Collector或存储系统,以便进一步处理和分析。Reporter可以使用不同的传输协议,如HTTP、gRPC或消息队列,将Span数据发送给Collector。
  3. Collector接收来自Reporter的Span数据,并进行处理和存储。它可以将Span数据存储在适当的后端存储系统中,如关系型数据库、时序数据库或日志

0x01.分布式链路追踪中间件介绍&选型

业界比较热门的链路追踪中间件选型主要有四种:Zipkin Jaeger SkyWalking Pinpoint

a.介绍

  1. Zipkin:
    • Zipkin 是 Twitter 开发并开源的一个分布式跟踪系统,它帮助收集服务之间调用的定时数据,以解决微服务架构中的延迟问题。它具有良好的社区支持和广泛的库和框架集成。
    • Zipkin 提供了一个简单的 UI 来查看请求跟踪信息和性能数据,但可能不如其他一些工具的功能强大。
    • 用 Java 编写,因此在 JVM 堆栈中表现出良好的性能。
  2. Jaeger:
    • Jaeger 是 Uber 开发的开源分布式追踪系统,灵感来自 Google 的 Dapper 和 OpenZipkin 社区。它可以追踪和可视化微服务之间的请求流程。
    • Jaeger 提供了一个较为复杂且功能强大的 UI,允许用户深入探查请求详细信息。
    • Jaeger 有广泛的语言支持,包括 Go、Java、Node、Python 和 C++。
    • Jaeger 同时支持 OpenTracing 和 OpenTelemetry。
  3. SkyWalking:
    • SkyWalking 是一个观察性分析平台和应用性能管理系统。它提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。
    • SkyWalking UI 提供了全面的服务、服务实例和端点的度量分析,包括拓扑图和依赖性分析。
    • SkyWalking 支持许多语言,包括 Java、.Net Core 和 NodeJS。
  4. Pinpoint:
    • Pinpoint 是由韩国 Naver 公司开发的开源 APM (Application Performance Management) 工具,专为大规模分布式系统设计。
    • Pinpoint 可以追踪分布式系统中的所有事务,并显示系统性能问题的详细信息。
    • 它也提供了丰富的 UI,展示了系统和组件之间的交互。
    • Pinpoint 主要用 Java 编写,特别适合追踪基于 Java 的系统。

b.对比

以下是四种分布式链路中间件的对比

pinpoint Zipkin Jaeger SkyWalking
OpenTracing兼容
go语言支持
存储 hbase ES,mysql,Cassandra,内存 ES,kafka,Cassandra,内存 ES,H2,mysql,TIDB,sharding sphere
传输协议支持 tcp/udp http/kafka http/grpc/thrift/kafka grpc/http
UI支持 提供了一个详细的服务拓扑图,可以查看服务之间的依赖关系,以及每个请求的完整调用链。Pinpoint 的 UI 也支持查看应用的性能指标和实时监控数据 其 UI 不如其他一些系统丰富,但它简洁明了,功能直观,能满足基本的追踪分析需求 Jaeger 的 UI 非常友好,支持搜索、过滤和查看追踪数据,展示追踪的详细信息,包括所有跨度的时间线视图,依赖关系图等。Jaeger 的 UI 还支持对追踪数据进行深入分析,包括性能优化和故障排查。 SkyWalking 提供了一个丰富的 UI,包括仪表盘、拓扑图、追踪视图和报警功能。SkyWalking 的 UI 是其主要优点之一,它提供了全面的分布式追踪、性能指标和应用性能管理(APM)功能。
可观测性粒度 Pinpoint 提供了非常详细的观察粒度,包括微服务级、方法级,甚至是代码级别的观察。Pinpoint 可以展示服务间的拓扑图,展示每个请求的完整调用链,包括方法调用和 SQL 查询的详细信息。(代码级别粒度仅支持java) Zipkin 主要提供微服务级别的观察 Jaeger 也是在微服务级别提供观察,可以查看服务之间的调用关系和延迟。对于方法调用栈级别的观察,Jaeger 可以通过 OpenTracing API 或者 OpenTelemetry API 在代码中添加自定义跟踪点。 SkyWalking 提供了微服务级和方法级别的观察,支持自动和手动追踪。它可以展示服务间的拓扑图、服务和服务实例的性能指标、数据库访问的详情,以及详细的追踪信息,包括每个请求的调用链和方法调用栈。
实现方式(代码侵入性) 相对较低 相对较低 相对较低
可扩展性 较差
告警支持 原生支持 原生不支持,需集成Prometheus 和 Grafana 等监控系统 原生不支持,需集成Prometheus 和 Grafana 等监控系统 原生支持

综上所述 考虑在Zipkin,Jaeger和SkyWalking中进行选择

0x02.部署和使用demo

a.部署

  • Zipkin

Zipkin提供了一个简单的方式来启动,你可以从其GitHub仓库下载已编译的jar文件,并直接运行。例如,你可以使用以下的命令来下载并运行Zipkin:

1
2
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar

此外,Zipkin也可以通过Docker容器来部署。以下是一个基本的Docker命令来运行Zipkin:

1
docker run -d -p 9411:9411 openzipkin/zipkin
  • Jaeger

Jaeger提供了多种部署方式,包括使用Docker,Kubernetes和OpenShift。以下是一个使用Docker来部署Jaeger的基本命令:

1
2
3
4
5
6
7
8
9
10
11
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 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:1.22
  • SkyWalking:

SkyWalking的部署稍微复杂一些,因为它需要一个数据库来存储监控数据。SkyWalking支持多种数据库,包括MySQL,Elasticsearch和H2。以下是一个基本的步骤来部署SkyWalking:

  1. 下载SkyWalking的release包,解压缩。
  2. 修改config/application.yml文件,设置正确的数据库连接信息。
  3. 运行bin/startup.sh来启动SkyWalking。

b.使用demo

1. Zipkin:

下面是一个简单的Go示例,展示了如何使用Zipkin进行服务集成和自定义Span采样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main

import (
"fmt"
"log"
"net/http"

"github.com/openzipkin/zipkin-go"
reporterhttp "github.com/openzipkin/zipkin-go/reporter/http"
"github.com/openzipkin/zipkin-go/propagation/b3"
zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http"
)

func main() {
// 创建Zipkin HTTP Reporter
reporter := reporterhttp.NewReporter("http://localhost:9411/api/v2/spans")
defer reporter.Close()

// 创建本地Endpoint
endpoint, err := zipkin.NewEndpoint("my-service", "localhost:8080")
if err != nil {
log.Fatal(err)
}

// 创建Tracer
tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint))
if err != nil {
log.Fatal(err)
}

// 创建HTTP中间件
zipkinMiddleware := zipkinhttp.NewServerMiddleware(
tracer,
zipkinhttp.SpanName("my-service"),
zipkinhttp.TagResponseSize(true),
zipkinhttp.SpanTags(map[string]string{"custom_tag": "value"}),
)

// 创建HTTP处理程序
handler := zipkinMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从HTTP请求中提取Span上下文
sc, _ := tracer.Extract(b3.ExtractHTTP(r))

// 创建一个新的Span
span := tracer.StartSpan("my-operation", zipkin.Parent(sc))
defer span.Finish()

// 在Span中添加标签
span.Tag("custom_key", "custom_value")

// 处理业务逻辑
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello, World!")
}))

// 启动HTTP服务器
http.ListenAndServe(":8080", handler)
}

2. Jaeger:

以下是一个使用Jaeger进行服务集成和自定义Span采样的Go示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package main

import (
"fmt"
"io"
"log"
"net/http"
"os"

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

func main() {
// 配置Jaeger
cfg := config.Configuration{
ServiceName: "my-service",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "localhost:6831",
},
}

// 初始化Jaeger Tracer
tracer, closer, err := cfg.NewTracer(
config.Logger(jaegerlog.StdLogger),
)
if err != nil {
log.Fatal(err)
}
defer closer.Close()
opentracing.SetGlobalTracer(tracer)

// 创建HTTP处理程序
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从HTTP请求中提取Span上下文
spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))

// 创建一个新的Span
span := tracer.StartSpan("my-operation", opentracing.ChildOf(spanCtx))
defer span.Finish()

// 在Span中添加标签
span.SetTag("custom_key", "custom_value")

// 处理业务逻辑
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello, World!")
})

// 创建HTTP中间件
jaegerMiddleware := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := tracer.StartSpan(r.URL.Path, opentracing.ChildOf(ctx))
defer span.Finish()

r = r.WithContext(opentracing.ContextWithSpan(r.Context(), span))

next.ServeHTTP(w, r)
})
}

// 添加Jaeger中间件
http.Handle("/", jaegerMiddleware(handler))

// 启动HTTP服务器
log.Fatal(http.ListenAndServe(":8080", nil))
}

3. SkyWalking:

以下是一个使用SkyWalking进行服务集成和自定义Span采样的Go示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

import (
"fmt"
"log"
"net/http"

"github.com/SkyAPM/go2sky"
"github.com/SkyAPM/go2sky/reporter"
"github.com/SkyAPM/go2sky/reporter/grpc"
"github.com/SkyAPM/go2sky/propagation"
)

func main() {
// 创建gRPC Reporter
r, err := grpc.NewReporter("127.0.0.1:11800")
if err != nil {
log.Fatal(err)
}
defer r.Close()

// 创建Tracer
tracer, err := go2sky.NewTracer("my-service", go2sky.WithReporter(r))
if err != nil {
log.Fatal(err)
}

// 创建HTTP处理程序
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从HTTP请求中提取Span上下文
spanCtx, _ := tracer.Extract(propagation.HTTPHeadersCarrier(r.Header))

// 创建一个新的Span
span, ctx, _ := tracer.CreateEntrySpan(r.Context(), "my-operation", go2sky.SpanFromContext(r.Context()), go2sky.Tag("custom_key", "custom_value"))
defer span.End()

// 处理业务逻辑
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello, World!")
})

// 创建HTTP中间件
skyWalkingMiddleware := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, _, _ := tracer.CreateEntrySpan(r.Context(), r.URL.Path, func(header string) error {
r.Header.Set(propagation.Header, header)
return nil
})
defer tracer.EndSpan(ctx)

r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}

// 添加SkyWalking中间件
http.Handle("/", skyWalkingMiddleware(handler))

// 启动HTTP服务器
log.Fatal(http.ListenAndServe(":8080", nil))
}