本文主要是介绍分布式应用全链路跟踪实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
随着分布式和微服务架构的发展,应用系统和服务组件之间的调用关系愈发复杂。如何精确的展示和快速定位服务单元之间的调用关系,实时观测应用系统整体链路情况,对应用系统的监控运维提出了挑战。本文简要介绍分布式应用链路跟踪的实现方式、OpenTracing规范以及对比不同全链路开源组件的实现。
1、全链路跟踪介绍
1.1 全链路跟踪背景
随着分布式和微服务技术的发展演进,越来越多的系统从单体应用向分布式微服务架构转型。在微服务架构下应用服务被拆分为不同的服务模块,这些服务模块之间的调用关系也变得错综复杂,客户端的连接请求可能需要在多个不同的服务单元之间进行流转。此时如果某个服务单元或者模块出现异常,很难快速直观的去定位到异常点,以及对整个系统的影响范围和影响程度、爆炸半径等。如下图所示为典型的微服务架构:
为了分析和解决分布式架构下故障的快速定位分析、故障影响的精确判断、服务模块之间的依赖关系和调用关系以及整个应用系统调用链路的整体观测性,引入了全链路跟踪技术。全链路跟踪技术可以将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。全链路跟踪的主要有以下功能点:
- 故障快速定位:通过调用链结合业务日志可以快速定位错误信息,迅速发现和解决问题。
- 链路性能可视化:各个阶段的链路耗时、服务依赖关系可以通过可视化界面展现出来,帮助开发人员更好地了解系统的运行状态和性能情况。
- 链路分析:通过分析链路耗时、服务依赖关系可以得到用户的行为路径,汇总分析应用在很多业务场景下。
下图是一个简单的微服务架构下的完整调用链路视图:
1.2 全链路跟踪实现原理
链路跟踪最早由Google提出,并发表了一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,介绍Google自研的分布式链路跟踪系统Dapper的实现原理和设计实现。随后出现了一众开源产品如Zipkin、Jaeger等,但是各个开源的分布式链路跟踪方案互不兼容,为了统一标准接口,诞生了OpenTracing。OpenTracing于2016年10月加入CNCF基金会,它是一个中立的(厂商无关、平台无关)分布式追踪的 API 规范,提供统一接口,可方便开发者在自己的服务中集成一种或多种分布式追踪的实现。
OpenTracing是一个分布式跟踪系统,它提供了一个平台无关、厂商无关的API,使得开发人员能够方便地添加(或更换)追踪系统的实现。在全链路跟踪中,OpenTracing通过在HTTP头中注入唯一标识符(trace ID)来实现追踪。这个唯一标识符可以关联所有的HTTP请求,从而形成一个完整的调用链。
1.2.1 Tracing定义
在应用系统中,Tracing指的是使用特定的日志记录程序的执行信息,与之相近的还有metrics和logging两个概念。
- Tracing:指使用特定的日志记录程序的执行信息,记录单个请求的处理流程,包括服务调用和处理时长等信息,用于诊断和优化分布式系统。
- Metrics:可聚合的数据,通常包括计数器(Counter)、量表(Gauge)和直方图(Histogram)等,用于度量和观察系统的行为。Metrics可以用来衡量一个系统的性能,例如请求的数量、响应的时间等。
- Logging:用于记录离散的事件,包含程序执行到某一点或某一阶段的详细信息。Logging可以用来记录程序的行为,帮助开发人员调试和解决问题。
Tracing、Metrics和Logging这三种监控类型交集的情况比较常见:
- Logging&Metrics:Metrics提供系统层面的统计数据,如系统总请求量、响应时间等;logging则提供系统的详细行为,比如统计某段时间SQL各类请求访问的总数等;
- Metrics&Tracing:Metrics可以看做是宏观层面的tracing,tracing更关注微观层面某个请求的处理流程、处理时间等,比如单个请求中SQL执行时长、gpc调用次数等;
- Tracing&Logging:Logging记录的是一些离散的事件、tracing则是记录的这些请求的处理过程,比如在请求过程中详细的处理记录日志等信息。
在分布式系统中,Tracing、Metrics和Logging作为三种不同类型的监控方式,侧重点不同,并且有不同的工具来实现:Tracing基于OpenTracing规范实现请求流程的链路分析、Metrics基于Prometheus实现应用系统指标的监控分析、Logging基于ELK或EFK实现日志数据的采集和分析。
1.2.2 OpenTracing规范
OpenTracing的出现主要是为了解决不同的分布式链路追踪平台的API兼容问题,通过提供与平台和厂商无关API的方式,使得开发人员能够很方便地切换追踪系统。在OpenTracing中有几个基本概念:
1)Span
Span是分布式跟踪的主要构建块,表示分布式系统中完成的单个工作单元。Span之间通过嵌套或者顺序排列建立逻辑因果关系。在OpenTracing中的一条Trace被认为是一个由多个Span组成的有向无环图(DAG)。每个Span根据OpenTracing规范封装了以下状态:
- 一个操作名称
- 开始时间戳和结束时间戳
- Span Tag:一组键值对构成的Span标签集合,其中键必须为字符串类型,值可以是字符串、bool值或者数字;
- Span Log:一组Span的日志集合;
- SpanContext:Trace的全局上下文信息;
- References:Span之间的引用关系;
在OpenTracing的一条Trace中,一个Span可以和多个Span之间存在因果关系,在OpenTracing中定义了ChildOf和FollowsFrom两种引用关系,如下所示:
- ChildOf关系:一个Span可能是一个父级Span的孩子,即为ChildOf关系。比如在一个http请求中调用服务端产生的span和发起调用的客户端的span,就构成ChildOf关系。
- FollowsFrom关系:在分布式系统中,一些上游系统不依赖于下游系统的执行结果,比如上游系统通过消息队列异步方式向下游系统发送消息,这样下游系统对应的子Span和上游系统对应的父Span之间就是FollowsFrom关系。
Causal relationships between Spans in a single Trace[Span A] ←←←(the root span)|+------+------+| |[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)| |[Span D] +---+-------+| |[Span E] [Span F] >>> [Span G] >>> [Span H]↑(Span G `FollowsFrom` Span F)
2)Tags
每个Span可以有多个键值对形式的Tags,Tags是没有时间戳的,只是为Span添加一些简单解释和补充信息。
3)Logs
每个Span可以进行多次Logs操作,每一次Logs操作,都需要带一个时间戳,以及一个可选的附加信息。
4)SpanContext
SpanContext表示进程边界,在跨进调用时需要将一些全局信息,例如TraceId、当前SpanId等信息封装到Baggage中传递到另一个进程中。
备注:在OpenTracing官网上看到OpenTracing相关的技术已经迁移到OpenTelemetry上。OpenTelemetry是OpenTracing和OpenCensus两个项目的合并,是一个更加强大的监控解决方案。它可以收集所有类型的遥测数据,如日志、指标和调用链,并且是一个开箱即用的API、SDK和库的合集。其中一条trace如下所示:
{"name": "hello-greetings","context": {"trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2","span_id": "0x5fb397be34d26b51"},"parent_id": "0x051581bf3cb55c13","start_time": "2022-04-29T18:52:58.114304Z","end_time": "2022-04-29T22:52:58.114561Z","attributes": {"http.route": "some_route2"},"events": [{"name": "hey there!","timestamp": "2022-04-29T18:52:58.114561Z","attributes": {"event_attributes": 1}},{"name": "bye now!","timestamp": "2022-04-29T18:52:58.114585Z","attributes": {"event_attributes": 1}}]
}
其中包括span_id和parent_id、traceid、start_time和end_time、attributes和events等信息。
1.2.3 全链路Trace示例
回到1.1中微服务架构下的完整调用链路视图,在服务调用的时候加上spanid、traceid和parentid,其中traceid为链路请求全局唯一id。如下所示:
- 节点Service1:SpanID=AAA1,ParentID=null,接收到客户端请求并发送请求到Service 2
- 节点Service2:SpanID=BBB1,ParentID=AAA1,接收到Service 1的请求并发送请求到下一步
- 节点缓存服务:SpanID=CCC1,ParentID=BBB1,接收到Service2的请求并返回结果
- 节点Service3:SpanID=DDD1,ParentID=BBB1,接收到Service2的请求并发送请求到下一步
- 节点Service4:SpanID=EEE1,ParentID=BBB1,接收到Service2的请求并发送请求到下一步
- 节点数据库服务:SpanID=FFF1,ParentID=DDD1,接收到Service3的请求并返回结果;SpanID=GGG1,ParentID=EEE1,接收到Service4的请求并返回结果
通过ParentID找到父节点,并通过全局唯一的traceID实现链路跟踪。
1.3 全链路跟踪开源组件
自Google Dapper系统开放以来,出现很多分布式链路跟踪的产品,如Pinpoint、Zipkin、Skywalking、Jaeger等,接下去将简要介绍对比。
1)Pinpoint
Pinpoint是由一个韩国团队实现并开源,针对Java编写的大规模分布式系统设计,通过JavaAgent的机制做字节代码植入,实现加入traceid和获取性能数据的目的,对应用代码零侵入。
官方网站:https://github.com/pinpoint-apm/pinpoint
2)SkyWalking
SkyWalking是一款国产的开源框架,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计的分布式系统的应用程序性能监视工具,包括了分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。SkyWalking支持Java、.Net Core、PHP、NodeJS、Golang等多种语言探针,支持Envoy + Istio构建的Service Mesh。
SkyWalking的客户端通过HTTP或gRPC方式向SkyWalking收集器发送链路调用数据。SkyWalking收集器对接收到的链路信息进行分析和聚合,并存储到服务端数据库,支持的存储组件有ES、H2、MySQL、TiDB和Sharding Sphere等。SkyWalking UI则提供了链路调用信息的可视化和检索。以下为Skywalking架构图:
官方网站:http://skywalking.apache.org/
3)Zipkin
Zipkin是由Twitter开源,是分布式链路调用监控系统,聚合各业务系统调用延迟数据,达到链路调用监控跟踪。Zipkin基于Google的Dapper论文实现,主要完成数据的收集、存储、搜索与界面展示。Zipkin组件如下图所示,包括Collector、Storage、Search和Web UI:
- Collector:Zipkin的一个常驻进程组件,主要负责提供对外部系统的接口,以收集、校验、存储以及索引追踪数据。
- Storage:存储模块,真正服务于Zipkin收集到的追踪数据的存储。默认情况下,Zipkin将这些信息存储在内存中,也可以修改存储策略,通过使用其他存储组件将追踪信息存储到数据库或ElasticSearch中。
- Zipkin Query Service(API):通过这个API组件提供外部访问接口,用于查找和检索链路调用信息。
- Web UI:Zipkin查询链路追踪的界面,用户可以通过简洁直观的web UI来检索数据和查看调用链路。
官方网站:https://zipkin.io/
4)云原生链路监控组件Jaeger
Jaeger是CNCF云原生项目之一,由Uber开源的分布式追踪系统,兼容Open Tracing API。Jaeger主要用于微服务的监控和请求追踪,支持分布式上下文传播、请求报错分析、服务的调用网络分析以及性能/延迟优化。Jaeger在架构上支持分布式追踪和性能监控,包括多个组件和模块,以实现数据收集、存储、查询和可视化等功能。如下图所示:
- jaeger-client:Client是应用程序的一部分,它负责在每个服务或应用中注入追踪代码。这个代码收集有关请求的元数据,例如开始时间、结束时间、服务名称等,并将其发送到Jaeger Agent。Client库有不同的版本,针对不同的编程语言,如Java、Python、Go等。
- jaeger-agent:Agent是一个运行在每个服务实例中的代理程序,它负责接收并转发从Client发送来的追踪数据。Agent通过UDP将收集到的追踪信息批量发送给Collector。
- jaeger-collector:Collector是Jaeger的核心组件之一,它负责接收从Agent发送来的追踪数据,并进行处理。这些数据首先会被验证和索引,然后被转换并存储在DB中。
- DB:Jaeger中用于存储追踪数据的组件。Jaeger支持多种存储后端,例如Kafka、Elasticsearch和Cassandra。数据在DB中以trace ID为单位进行存储,每个trace ID下都会保存该请求的完整调用链路信息。
- jaeger-query:用于检索链路调用信息,通过这个接口查询和展示存储在DB中的追踪数据。
官方网站:https://www.jaegertracing.io/
5)各种全链路跟踪方案对比
对比上述四种组件,Zipkin本身实现相对简单,指标的分析和展示需求需要二次开发工作;Jaeger是对Zipkin的优化,在WebUI和传输协议上进行了改进,但也无告警功能;Pinpoint不支持OpenTracing规范,并且不支持查询单个调用链,二次开发难度较高;SkyWalking功能较为齐全,探针性能损耗低,同时也支持多种语言的客户端,并且中文社区非常活跃。
接下去将重点介绍基于Skywalking的全链路跟踪实现。
参考资料:
- https://opentelemetry.io/docs/concepts/signals/traces/
- https://www.cnblogs.com/dalianpai/p/12853683.html
- https://blog.csdn.net/fegus/article/details/126498887
- https://blog.csdn.net/eight_eyes/article/details/117330608
- https://segmentfault.com/a/1190000040364737
这篇关于分布式应用全链路跟踪实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!