分布式链路追踪(全链路追踪)是分布式系统或者微服务架构中服务监控、性能优化的有效手段。分布式链路追踪有 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配置消息,配置采样
率等信息
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下发的配置,设置采样率等信息。
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.pngSkyWalking
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 逻辑上分为四部分: 探针, 平台后端, 存储和⽤户界⾯。
-
探针:基于不同的来源可能是不⼀样的, 但作⽤都是收集数据, 将数据格式化为 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。
在启动时加载 instrument agent
启动时加载 instrument agent,具体过程都在InvocationAdapter.c
的Agent_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⽅法
Zipkin
Zipkin 组件设计
Zipkin是⼀个分布式追踪系统。它有助于收集解决微服务架构中延迟
问题所需的时序数据。它管理这些数据的收集和查找。Zipkin的设计
基于 Google Dapper论⽂。
共有四个组件构成了 Zipkin:
- collector
- storage
- search
- web UI
Reporter是装配应⽤中⽤于向 Zipkin 发送数据的组件。Reporter 通过
Transport 发送追踪数据到 Zipkin 的 Collector,Collector 持久化数据
到 Storage 中。之后,API 从 Storage 中查询数据提供给 UI。
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 没有内置的身
份认证功能。
总结
- 非侵入性,并且对 JAVA, .NET 等语言支持,可以选择 SkyWalking, 接入成本较小 ;
- 如果采用 OpenTracing 集成接入,可以选择 Jaeger 或者 Zipkin ,侵入性比较大,接入成本中等;
- Service Mesh Tracing 接入 (如 istio tracing ),侵入性较小,接入成本较小 ;
在生产中使用,需要看团队研发力量和需求,1-3 种方案均可以实施。
下一章和大家分享 自研 分布式链路追踪 SDK 。
网友评论