链路追踪 研究03  SkyWalking_拦截器

整体主要分为三个部分:


1.skywalking-collector:链路数据归集器,数据可以保存在H2或ElasticSearch

2.skywalking-web:web的可视化管理后台,可以查看归集的数据

3.skywalking-agent:探针,用来收集和推送数据到归集器

链路追踪 研究03  SkyWalking_java_02

链路追踪 研究03  SkyWalking_数据_03

链路追踪 研究03  SkyWalking_拦截器_04

链路追踪 研究03  SkyWalking_java_05

链路追踪 研究03  SkyWalking_java_06

SkyWalking trace视图

链路追踪 研究03  SkyWalking_拦截器_07






源码分析

SkyWalking Agent 基于 JavaAgent 机制,实现应用透明接入 SkyWalking

agent启动入口类SkyWalkingAgent


链路追踪 研究03  SkyWalking_java_08

1.初始化agent配置

2.加载agent插件,创建出pluginFinder

3.根据byteBuddy,创建agentBuilder

链路追踪 研究03  SkyWalking_拦截器_09

链路追踪 研究03  SkyWalking_数据_10

4.2 调用每个service的prepare方法

4.3 调用每个service的boot方法,启动

4.4调用每个service的onComplete方法

链路追踪 研究03  SkyWalking_数据_11

链路追踪 研究03  SkyWalking_拦截器_12

Agent初始化时,会调用PluginBootstrap#loadPlugins,加载所有的插件,流程图如下:

链路追踪 研究03  SkyWalking_java_13

SkyWalking 通过 JavaAgent 机制,对需要拦截的类的方法,使用 byte-buddy 动态修改 Java 类的二进制,从而进行方法切面拦截,记录调用链路。

看具体的代码实现之前,想一下拦截会涉及到哪些元素 :

拦截切面 InterceptPoint

拦截器 Interceptor

拦截类的定义 Define :一个类有哪些拦截切面及对应的拦截器

链路追踪 研究03  SkyWalking_java_14

直接看增强实现enhance

1.enhanceClass 对静态方法增强

链路追踪 研究03  SkyWalking_java_15

InterceptPoint 拦截点,每个插件都需要实现这个拦截器接口

链路追踪 研究03  SkyWalking_java_16

链路追踪 研究03  SkyWalking_数据_17

链路追踪 研究03  SkyWalking_拦截器_18

  • 应用启动,Agent 向 Collector 注册应用
  • 注册应用成功后,Agent 向 Collector 注册应用实例

链路追踪 研究03  SkyWalking_java_19


​分布式​​链路追踪系统,链路的追踪大体流程如下:

  1. Agent 收集 Trace 数据。
  2. Agent 发送 Trace 数据给 Collector 。
  3. Collector 接收 Trace 数据。
  4. Collector 存储 Trace 数据到存储器,例如,数据库。

链路追踪 研究03  SkyWalking_拦截器_20

链路追踪 研究03  SkyWalking_数据_21

链路追踪 研究03  SkyWalking_java_22

链路追踪 研究03  SkyWalking_java_23

Span 只有三种实现类:

  • EntrySpan :入口 Span
  • LocalSpan :本地 Span
  • ExitSpan :出口 Span

每进入一个应用,EntrySpan ,栈深度+1, ExitSpan出口 ,栈深度-1

创建EntrySpan

父span存在,就直接start;父span不存在,就新建一个EntrySpan

创建exitSpan,原理类似

链路追踪 研究03  SkyWalking_拦截器_24


链路追踪 研究03  SkyWalking_拦截器_25

  • Collector 接收到 TraceSegment 数据后,进行构建
  • 【蓝色流程】构建成功,进行流式处理,最终存储到存储器( 例如,ES / H2 )。
  • 【粉色流程】构建失败,写入 Buffer 文件进行暂存。
  • 【绿色流程】后台线程,定时读取 Buffer 文件,重新提交构建。

存储分为ES和H2

先看下H2 文件数据库

链路追踪 研究03  SkyWalking_拦截器_26

EsDAO的实现,存储到ElasticSearch

链路追踪 研究03  SkyWalking_数据_27

mysql的存储

链路追踪 研究03  SkyWalking_java_28


JavaAgent原理

SkyWalking Agent 采用了微内核架构(Microkernel Architecture),那什么是微内核架构呢?微内核架构也被称为插件化架构(Plug-in Architecture),是一种面向功能进行拆分的可扩展性架构。在基于产品的应用中通常会使用微内核架构,例如,IDEA、Eclipse 这类 IDE 开发工具,内核都是非常精简的,对 Maven、Gradle 等新功能的支持都是以插件的形式增加的。

链路追踪 研究03  SkyWalking_数据_29

最终所有插件会由内核系统统一接入和管理:


1.内核系统必须知道要加载哪些插件,一般会通过配置文件或是扫描 ClassPath 的方式(例如前文介绍的 SPI 技术)确定待加载的插件;

2.内核系统还需要了解如何使用这些插件,微内核架构中需要定义一套插件的规范,内核系统会按照统一的方式初始化、启动这些插件;

3.虽然插件之间完全解耦,但实际开发中总会有一些意想不到的需求会导致插件之间产生依赖或是某些底层插件被复用,此时内核需要提供一套规则,识别插件消息并能正确的在插件之间转发消息,成为插件消息的中转站。


由此可见微内核架构的好处:

测试成本下降。从软件工程的角度看,微内核架构将变化的部分和不变的部分拆分,降低了测试的成本,符合设计模式中的开放封闭原则。

稳定性。由于每个插件模块相对独立,即使其中一个插件有问题,也可以保证内核系统以及其他插件的稳定性。

可扩展性。在增加新功能或接入新业务的时候,只需要新增相应插件模块即可;在进行历史功能下线时,也只需删除相应插件模块即可。

SkyWalking Agent 就是微内核架构的一种落地方式。在前面的课时中我已经介绍了 SkyWalking 中各个模块的功能,其中 apm-agent-core 模块对应微内核架构中的内核系统,apm-sdk-plugin 模块中的各个子模块都是微内核架构中的插件模块。


链路追踪 研究03  SkyWalking_数据_30

链路追踪 研究03  SkyWalking_java_31

链路追踪 研究03  SkyWalking_java_32

链路追踪 研究03  SkyWalking_拦截器_33

链路追踪 研究03  SkyWalking_数据_34

类加载之后,使用Java反射API就可以访问它了。如果没有指定其他构造器的话,Byte Buddy将会生成类似于父类的构造器,因此生成的类可以使用默认的构造器。

链路追踪 研究03  SkyWalking_java_35

链路追踪 研究03  SkyWalking_拦截器_36

链路追踪 研究03  SkyWalking_java_37

链路追踪 研究03  SkyWalking_数据_38

1)Byte Buddy介绍

Byte Buddy 是一个开源 Java 库,其主要功能是帮助用户屏蔽字节码操作,以及复杂的; 基于ASM API实现

Instrumentation API 。Byte Buddy 提供了一套类型安全的 API 和注解,我们可以直接使用这些 API 和注解轻松实现复杂的字节码操作。另外,Byte Buddy 提供了针对 Java Agent 的额外 API,帮助开发人员在 Java Agent 场景轻松增强已有代码。

学习完上面方法后,我们基于java agent写一个统计方法耗时流程,此时我们需要将 Java Agent 与Byte Buddy 结合使用,统计 com.itheima.agent包下所有方法的耗时。

链路追踪 研究03  SkyWalking_拦截器_39

链路追踪 研究03  SkyWalking_拦截器_40

链路追踪 研究03  SkyWalking_拦截器_41

链路追踪 研究03  SkyWalking_数据_42

Byte Buddy 对比JdkProxy  或者 Cglib

从Java动态代理实现上来看,可分为两种策略:一种是操作字节码,创建新类或者修改现有类,比如ASM/byte-buddy/Java动态代理;另一种是使用Java编码方式创建新类或者修改现有类,比如javassist。

几种动态编程方法相比较,在性能上Javassist高于反射,但低于ASM,因为Javassist增加了一层抽象。在实现成本上Javassist和反射都很低,而ASM由于直接操作字节码,相比Javassist源码级别的api实现成本高很多。几个方法有自己的应用场景,比如Kryo使用的是ASM,追求性能的最大化。而BeanCopyUtil采用的是Javassist,在对象拷贝的性能上也已经明显高于其他的库,并保持高易用性。实际项目中推荐先用Javassist实现原型,若在性能测试中发现Javassist成为了性能瓶颈,再考虑使用其他字节码操作方法做优化。