分布式链路追踪实践(一)

分布式链路追踪方案对比

Posted by ethan.luo on December 5, 2020

分布式链路追踪(全链路追踪)是分布式系统或者微服务架构中服务监控、性能优化的有效手段。分布式链路追踪有 Jaeger, Zipkin, SkyWalking 等方案,我们详细讲解它们的架构原理;

  • 分布式链路追踪方案选型
  • 分布式链路追踪实践
  • 分布式链路追踪 SDK

⾯向 DevOps 的诊断与分析系统

  • Logging - 集中式⽇志系统: ⽤于记录离散的事件。例如,应⽤程序的调试信息或错误信息。它是我们诊断问题的依据。
  • Tracing - 分布式追踪系统: ⽤于记录请求范围内的信息。例如,⼀次远程⽅法调⽤的执⾏过程和耗时。它是我们排查系统性能问题的利器。
  • Metrics - 集中式度量系统: ⽤于记录可聚合据。的数例如,队列的当前深度可被定义为⼀个度量值,在元素⼊队或出队时被更新;HTTP 请求个数可被定义为⼀个计数器,新请求到来时进⾏累加。

所以 Logging,Metrics 和 Tracing 有各⾃专注的部分。

ltm.png

全链路 - 分布式追踪

方案对比:

project.png

  • Drapper:google的Drapper–未开源,最早的APM

  • CAT:⼤众点评跨服务的跟踪功能与点评内部的RPC框架集成,这部分未开源且项⽬在2014.1已经停⽌维护。服务粒度的监控,通过代码埋点的⽅式来实现监控,⽐如: 拦截器,注解,过滤器等,对代码的侵⼊性较⼤,集成成本较⾼。

  • Hydra:京东Hydra与dubbo框架集成,对于服务级别的跟踪统计,现有业务可以⽆缝接⼊。对于细粒度的兴趣点,需要业务⼈员⼿动添加.开源项⽬已于2013年6⽉停⽌维护

  • PinPoint-naver:字节码探针技术,代码⽆侵⼊,体系完善不易修改,⽀持java,技术栈⽀持dubbo.其他语⾔社区⽀援中

  • Zipkin:java⽅便集成于springcloud,社区⽀持的插件也包括dubbo,rabbit,mysql,httpclient等(https://github.com/openzipkin/brave/tree/master/instrumentation),同时⽀持php,go,js等语⾔客户端,界⾯功能较为简单,本身⽆告警功能,可能需要⼆次开发。代码⼊侵度⼩。

  • Jaeger:Uber-Jaeger⽀持java/c++/go/node/php,在界⾯上较为完善(对⽐zipkin),但是也⽆告警功能。代码⼊侵度⼩。dubbo⽬前⽆插件⽀持,可⼆次开发。

  • Skywalking:华为开源,类似于PinPoint,⽬前还在apache孵化中,⽹上吞吐量对⽐中强于pinpoint (实际未验证), 本身⽀持dubbo

Jaeger

架构设计

Jaeger是Uber开发的⼀套分布式追踪系统,受启发于 dapper 和OpenZipkin,兼容 OpenTracing 标准,CNCF的开源项⽬。

Jaeger 架构设计:

jaeger.png

  • Jaeger Client - 为不同语⾔实现了符合 OpenTracing 标准的 SDK。应⽤程序通过 API 写⼊数据,client library 把 trace 信息按照应⽤程序指定的采样策略传递给 jaeger-agent。

  • Agent - 是⼀个监听在 UDP 端⼝上接收 span 数据的⽹络守护进程,它会将数据批量发送给 collector。它被设计成⼀个基础组件,推荐部署到所有的宿主机上。Agent 将 client library 和 collector 解耦,为 client library 屏蔽了路由和发现 collector 的细节。

  • Collector - 接收 jaeger-agent 发送来的数据,然后将数据写⼊后端存储。Collector 被设计成⽆状态的组件,因此您可以同时运⾏任意数量的 jaeger-collector。

  • Data Store - 后端存储被设计成⼀个可插拔的组件,⽀持将数据写⼊ cassandra、elastic search。

  • Query - 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进⾏展示。Query 是⽆状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后⾯。

Agent 初始化类图

TUDPTransport:基于Thrift框架的UDP传输类。 TBufferedServer:基于TUDPTransport的UDP服务器类。 Processor:消息处理类,提供Serve和Stop两个接⼝。 ThriftProcessor:消息传递类,⽤于异步的将从UDPServer 这边接收到的span消息,送⾄AgentProcessor处理。 AgentProcessor:根据协议区分jaeger和zipkin消息,接收 并处理method为emitBatch的消息,发送⾄Reporter。 HTTPServerConfiguration:⽤于配置HTTPServer。 HTTPServer⽤于接收Collector的HTTP配置消息,配置采样 率等信息

jaeger_agent.png

Agent初始化序列图

  • CreateReporter:app/builder.go 中提供createMainReporter接⼝,在接⼝中调⽤Builder.CreateReporter接⼝。CreateReporter接⼝在 app/report/tchannel/builder.go中实现。CreateReporter接⼝中创建了⼀个tchannel类型的Reporter。

  • NewAgentProcessor:根据zipkin和jaeger两种协议类型,agent会创建各⾃的AgentProcessor。Jaeger类型的AgentProcessor实现在 thrift-gen/agent/agent.go。得到传回的对象后作为handler传⼊ ThriftProcessor。

  • getUDPServer:创建基于 thrift 的 UDP 服务器,并作为⼊参传⼊ ThriftProcessor。

  • GetHTTPServer:创建基于HTTP的服务器,⽤于处理 Collector下发的配置,设置采样率等信息。

jaeger_agent_ sequence.png

Agent数据流序列图

  • Serve:Agent为不同协议的ThriftProcessor创建多个协程,并调⽤其Serve接⼝。

  • processBuffer:在ThriftProcessor的Serve接⼝中根据配置创建多个协程⽤于并发处理span消息。

  • DataRecd:TBufferedServer在Buffer池的机制,避免了空间反复的 new 和 delete。此处将⽤完的数据包返回TBufferedServer,以便下次接收数据时再次使⽤。

  • Process:ThriftProcessor将接收到的数据转换成对应的协议格式后,传递到AgentProcessor中。

  • Process:AgentProcessor 解析消息 Method 头,如果为 EmitBatch 则调⽤对应回调进⾏处理。当前只⽀持EmitBatch消息。

综上所述,可以看出来Agent模块主要通过tchannel接收本机的UDP消息(实为span消息),再传递⾄thrift框架的Reporter,发送⾄Collector。在Agent消息处理过程中,都为⼆进制协议消息,⾮明⽂。Agent不对消息内容做任何修改。

jaeger_agent_data_sequence.png

SkyWalking

SkyWalking 架构设计

SkyWalking 的核⼼是数据分析和度量结果的存储平台,通过 HTTP 或 gRPC ⽅式向SkyWalking Collecter 提交分析和度量数据,SkyWalking Collecter 对数据进⾏分析和聚合,存储到 Elasticsearch、H2、MySQL、TiDB 等其⼀即可,最后我们可以通过 SkyWalking UI 的可视化界⾯对最终的结果进⾏查看。SkyWalking ⽀持从多个来源和多种格式收集数据:多种语⾔的 Skywalking Agent 、Zipkin v1/v2 、Istio 勘测、Envoy 度量等数据格式。

skywalking.png

  • SkyWalking 是针对分布式系统的 APM 系统,也被称为分布式追踪系统全⾃动探针监控,不需要修改应⽤程序代码。(查看⽀持的中间件和组件库列表:https://github.com/apache/incubator/skywalking )
  • ⽀持⼿动探针监控, 提供了⽀持 OpenTracing 标准的SDK。覆盖范围扩⼤到 OpenTracing-Java ⽀持的组件。 (查看OpenTracing组件⽀持表:https://github.com/opentracing-contrib/meta )
  • ⾃动监控和⼿动监控可以同时使⽤,使⽤⼿动监控弥补⾃动监控不⽀持的组件,甚⾄私有化组件。纯 Java 后端分析程序,提供 RESTful 服务,可为其他语⾔探针提供分析能⼒。⾼性能纯流式分析。

skywalking_arch2.png

SkyWalking 逻辑上分为四部分: 探针, 平台后端, 存储和⽤户界⾯。

  • 探针:基于不同的来源可能是不⼀样的, 但作⽤都是收集数据, 将数据格式化为 SkyWalking 适⽤的格式

  • 平台后端:是⼀个⽀持集群模式运⾏的后台, ⽤于数据聚合, 数据分析以及驱动数据流从探针到⽤户界⾯的流程. 平台后端还提供了各种可插拔的能⼒, 如不同来源数据(如来⾃ Zipkin)格式化, 不同存储系统以及集群管理. 你甚⾄还可以使⽤观测分析语⾔来进⾏⾃定义聚合分析

  • 存储:是开放式的. 你可以选择⼀个既有的存储系统, 如 ElasticSearch, H2 或 MySQL 集群 (Sharding-Sphere 管理), 也可以选择⾃⼰实现⼀个存储系统. 当然, 我们⾮常欢迎你贡献新的存储系统实现

  • ⽤户界⾯:对于 SkyWalking 的最终⽤户来说⾮常炫酷且强⼤. 同样它也是可定制以匹配你已存在的后端的

探针限制

进程内传播在⼤多数情况下成为可能。许多⾼级编程语⾔(如 Java, .NET)都是⽤于构建业务系统. ⼤部分业务逻辑代码对于每⼀个请求来说都运⾏在同⼀个线程内, 这使得传播是基于线程 ID 的, 以确保上下⽂是安全的.仅仅对某些框架和库奏效。因为是代理来在运⾏时修改代码的, 这也意味着代理插件开发者事先就要知道 所要修改的代码是怎么样的. 因此, 在这种探针下通常会有⼀个已⽀持的列表清单.⽀持服务列表: https://github.com/apache/skywalking/blob/master/docs/en/setup/service-agent/java-agent/Supported-list.md

跨线程可能并⾮总是奏效 如上所述, 每个请求的代码⼤都运⾏在⼀个线程之内, 对于业务代码来说尤其如此. 但是在其他⼀些场景下, 它们也会在不同线程下⼯作, ⽐如指派任务到其他线程, 任务池, 以及批处理. 对于⼀些语⾔, 可能还提供了协程或类似的概念如 Goroutine, 使得开发者可以低开销地来执⾏异步操作, 在这些场景下, ⾃动打点可能会遇到⼀些问题。

动态探针技术

字节码增加技术(有的叫动态探针技术)来实现⽆侵⼊式的调⽤链采集。其核⼼实现原来还是基于JVM的javaagent机制来实现: “-javaagent:$AGENT_PATH/-bootstrap-$VERSION.jar “ 来指定skywalking agent加载路径,在启动的时候agent将在加载应⽤class⽂件之前做拦截并修改字节码,在class⽅法调⽤的前后加上链路采集逻辑,从⽽实现链路采集功能。 javaAgent的底层机制主要依赖JVMTI ,JVMTI全称JVM Tool Interface,是JVM暴露出来的⼀些供⽤户扩展的接⼝集合。JVMTI是基于事件驱动的,JVM每执⾏到⼀定的逻辑就会调⽤⼀些事件的回调接⼝(如果有的话),这些接⼝可以供开发者扩展⾃⼰的逻辑。但JVMTI都是⼀些接⼝合集,需要有接⼝的实现,这就⽤到了java的instrument,可以理解instrument是JVMTI的⼀种实现,为JVM提供外挂⽀持。

javaagent具体实现

使⽤:java -javaagent:myagent.jar=mode=test Test javaagent 的主要功能如下: • 可以在加载 class ⽂件之前做拦截,对字节码做修改 • 可以在运⾏期对已加载类的字节码做变更,但是这种情况下会有很多的限制,后⾯会详细说 • 还有其他⼀些⼩众的功能

◦获取所有已经加载过的类 ◦获取所有已经初始化过的类(执⾏过 clinit ⽅法,是上⾯的⼀个⼦集) ◦获取某个对象的⼤⼩ ◦将某个 jar 加⼊到 bootstrap classpath ⾥作为⾼优先级被 bootstrapClassloader 加载 ◦将某个 jar 加⼊到 classpath ⾥供 AppClassloard 去加载 ◦设置某些 native ⽅法的前缀,主要在查找 native ⽅法的时候做规则匹配

JVMTI 全称 JVM Tool Interface,是 JVM 暴露出来的⼀些供⽤户扩展的接⼝集合。JVMTI 是基于事件驱动的,JVM 每执⾏到⼀定的逻辑就会调 ⽤⼀些事件的回调接⼝(如果有的话),这些接⼝可以供开发者扩展⾃⼰的逻辑。⽐如最常⻅的,我们想在某个类的字节码⽂件读取之后、类 定义之前修改相关的字节码,从⽽使创建的 class 对象是我们修改之后的字节码内容,那就可以实现⼀个回调函数赋给 jvmtiEnv(JVMTI 的运 ⾏时,通常⼀个 JVMTIAgent 对应⼀个 jvmtiEnv,但是也可以对应多个)的回调⽅法集合⾥的 ClassFileLoadHook,这样在接下来的类⽂件加 载过程中都会调⽤到这个函数中。

instrument agent

instrument agent 实现了Agent_OnLoad和Agent_OnAttach两⽅法,也就是说在使⽤时,agent既可以在启动时加载,也可以在运⾏时动态加 载。其中启动时加载还可以通过类似-javaagent:myagent.jar的⽅式来间接加载 instrument agent,运⾏时动态加载依赖的是 JVM 的 attach 机制 ( JVM Attach 机制实现),通过发送 load 命令来加载 agent。

skywalking_agent_structure.png

在启动时加载 instrument agent 启动时加载 instrument agent,具体过程都在InvocationAdapter.cAgent_OnLoad⽅法⾥,这⾥简单描述下过程: 创建并初始化 JPLISAgent 监听 VMInit 事件,在 vm 初始化完成之后做下⾯的事情: 创建 InstrumentationImpl 对象 监听 ClassFileLoadHook 事件 调⽤ InstrumentationImpl 的loadClassAndCallPremain⽅法,在这个⽅法⾥会调⽤ javaage 在运⾏时加载 instrument agent 上⾯会通过 JVM 的 attach 机制来请求⽬标 JVM 加载对应的 agent,过程⼤致如下: 创建并初始化 JPLISAgent 解析 javaagent ⾥ MANIFEST.MF ⾥的参数 创建 InstrumentationImpl 对象 监听 ClassFileLoadHook 事件 调⽤ InstrumentationImpl 的loadClassAndCallAgentmain⽅法,在这个⽅法⾥会调⽤ javaagent ⾥ MANIFEST.MF ⾥指定的Agent-Class类的 agentmain⽅法

skywalking_ui.png

Zipkin

Zipkin 组件设计

Zipkin是⼀个分布式追踪系统。它有助于收集解决微服务架构中延迟 问题所需的时序数据。它管理这些数据的收集和查找。Zipkin的设计 基于 Google Dapper论⽂。 共有四个组件构成了 Zipkin:

  • collector
  • storage
  • search
  • web UI Reporter是装配应⽤中⽤于向 Zipkin 发送数据的组件。Reporter 通过 Transport 发送追踪数据到 Zipkin 的 Collector,Collector 持久化数据 到 Storage 中。之后,API 从 Storage 中查询数据提供给 UI。

zipkin_arch.png

Zipkin 架构设计

  • Transport: 装配库发送的跨度必须由装配的服务传输到 Collector。 有三种主要的传输类型: HTTP、Scribe 和 Kafka。更多信息查看跨度接收器。

  • Collector: ⼀旦追踪数据抵达 Zipkin Collector 守护进程,Zipkin Collector 为了查询,会对其进 ⾏校验、存储和索引。

  • Storage: Zipkin 最初是构建在将数据存储在 Cassandra 中,因为 Cassandra 易跨站,⽀持 灵活的 schema,并且在 Twitter 内部被⼤规模使⽤。然⽽,我们将这个组件做成了 可插拔式的。在 Cassandra 之外,我们原⽣⽀持 ElasticSearch 和 MySQL。可作为 第三⽅扩展提供给其它后端。

Zipkin 查询服务:

⼀旦数据被存储索引,我们就需要⼀种⽅式提取它。查询守护进程提供了⼀个简单 的 JSON API 查询和获取追踪数据。API 的主要消费者就是 Web UI。

Web UI:

我们创建了⼀个⽤户图形界⾯为追踪数据提供了⼀个漂亮的视图。Web UI 提供了基 于服务、时间和标记(annotation)查看追中数据的⽅法。注意:UI 没有内置的身 份认证功能。

zipkin_arch2.png

zipkin_community_support.png

zipkin_ui.png

zipkin_ui2.png

总结

  1. 非侵入性,并且对 JAVA, .NET 等语言支持,可以选择 SkyWalking, 接入成本较小 ;
  2. 如果采用 OpenTracing 集成接入,可以选择 Jaeger 或者 Zipkin ,侵入性比较大,接入成本中等;
  3. Service Mesh Tracing 接入 (如 istio tracing ),侵入性较小,接入成本较小 ;

在生产中使用,需要看团队研发力量和需求,1-3 种方案均可以实施。 下一章和大家分享 自研 分布式链路追踪 SDK 。