skyWalking-javaAgent解析
skyWalking java如何收集数据
skyWalking被java程序使用的时候,需要添加-javaagent参数,javaagent的作用其实很多,但是简单来说,就是启动前替换java类
代码引用基于v8.7.0
版本
instrument
jdk5引入了java.lang.instrument
,该包提供了一个Java编程API,可以用来开发增强Java应用程序的工具,例如监视它们或收集性能信息. 使用instrumentation
,开发者可以构建一个独立于应用程序的代理程序(Agent,用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义.有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和Java类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了.
这里的AOP是JVM级别的AOP,已经和业务无关,和业务有关的AOP就不能用这个类型,比如事务,缓存,因为这里的AOP完全与业务解耦了,所以,基本上,instrumentation
一般只能用来监控
AOP
其实监控,就是方法执行前,执行后做一些动作.平常我们其实也经常用AOP,比如日志,缓存,事务等等,所以说,skyWalking收集数据的思路就是使用AOP.AOP的实现就是创建代理类,也就是修改类,做处理.
自己动手写一个javaagent
- 编写带有特定方法的类
1
public static void premain(String agentArgs, Instrumentation inst){}
- 打包成jar,并且添加manifest属性
Premain-Class: pers.apricot.MyAgent
- 应用添加
-javaagent:agent.jar=${agentArgs}
启动,就可以看到效果
我这里写了一个简单的不带aop的agent
org.apache.**.agent.SkyWalkingAgent 解析
这里可以看到其明显使用了我们刚才介绍的instrument
,并且这里有个新的框架bytebuddy
.
bytebuddy
简介
动态修改类有两派,一派是java source类型,就是用string新建类和修改类,这类的典型代表是javaassist,还有一类就是非常硬核的修改bytecode,如asm,前者比较简单,后者需要掌握的就比较多.
bytebuddy
属于java source派,可以动态的生成java字节码文件,比起我们自己进行字节码文件的生成,它屏蔽了底层细节,提供一套统一易上手的Api,简化了字节码增强的学习难度,而且bytebuddy
针对javaagent做了封装,
我们可以非常简单的使用它的api挑选我们自己想要增强的类,来增强
ByteBuddy使用入坑
skyWalking自定义插件
skyWalking
虽然使用byteBuddy
但是,自己定义了一套插件机制,而不是直接使用字节码增加
skyWalking
自定义了一套插件体系,并且skyWalking
官方维护了很多常用的插件,开发完插件以后,放到plugins
目录即可生效
这里可以看到同一个插件,因为版本不同,也需要不同plugin,可见,插件的开发也是一项繁重的体力活
概述
追踪的基本方法是拦截Java方法,使用字节码操作技术和AOP概念.SkyWalking包装了字节码操作技术,并追踪上下文的传播.所以你只需要定义拦截点(换句话说就是Spring的切面).
拦截
skywalking
提供了两类通用的定义去拦截构造方法,实例方法和静态方法
ClassInstanceMethodsEnhancePluginDefine
定义了Contructor
和instance method
实例方法拦截点ClassStaticMethodsEnhancePluginDefine
定义了class method
拦截点- 继承
ClassEnhancePluginDefine
去设置所有的拦截点,不常用
匹配目标类
定义ClassInstanceMethodsEnhancePluginDefine
的子类,重写enhanceClass
去匹配目标类.
ClassMatch
ClassMatch表示如何去匹配目标类,这里有四种方法
- byName, 通过类的全限定名(Fully Qualified Class Name, 即 包名 + . + 类名).
- byClassAnnotationMatch, 根据目标类是否存在某些注解.
- byMethodAnnotationMatch, 根据目标类的方法是否存在某些注解.
- byHierarchyMatch, 根据目标类的父类或接口
注意事项:
- 禁止使用 .class.getName() 去获取类名, 建议你使用文本字符串, 这是为了避免 ClassLoader 的问题.byAnnotationMatch 不支持从父类继承来的注解.
- 除非确实必要, 否则不建议使用 byHierarchyMatch, 因为使用它可能会触发拦截许多预期之外的方法, 会导致性能问题和不稳定.
定义方法拦截
重新getInstanceMethodsInterceptPoints
方法
- 重新方法
- 定义拦截方法的几种逻辑
- 方法匹配
- 放回方法拦截器的全类名,这个
ElementMatcher
是byteBuddy
的内容,所以,skyWalking
这块的实现直接套用了byteBuddy
的东西,省了一些代码 - 是否覆盖参数,如果你需要在拦截器中更改引用的参数,就需要返回
true
,修改InstanceMethodsAroundInterceptor
的allArguments
才起效
skywalking-plugin.def 添加定义
key是语义化的名字即可,然后后面是Instrumentation
的全限定类名,也就是定义拦截的类,拦截的方法,拦截的逻辑,这三个要素的类
1 | key=com.any.SomeClass |
实现拦截器
- 接口名称
- 方法执行前,获取该对象,该方法,传的参数,参数类型,方法拦截结果
- 方法执行后,返回的结果,这里可以返回自定义的结果,也s就是返回代理对象
skyWalking jdbc 插件解析
- 定义拦截的类
- 定义拦截的方法
- 定义拦截的逻辑,创建span
- 设定cmponent
- 设定layer
- 添加定义
自定义开发组件
在项目中,我使用了RestTemplate
和外部的接口交互,获取数据,假定我想记录我发送了多少次请求,每次请求的内容是什么,返回的内容是什么,以这个作为需求来
开发一个skyWalking
的插件
拦截的类和方法
我主要就用了这一个方法,名字是postForEntity
,有三个重载的方法,但是我只拦截这一个
Instrumentation编码
- 拦截的类名
- 拦截的方法,需要按照需要精确定义
- 拦截器
Interceptor 编码
我根据需求,根据入参和返回的参数,获取我需要数据,完成了http发送请求请求参数和返回数据的获取
- 创建
ContextCarrier
- 定义
component
- 定义
layer
- 设置
params
- 设置
body
skywalking-plugin.def
根据规范设置Instrumentation
查询效果
- 我首先打包这个jar
- 然后放到
plugins
目录 - 依次启动服务,然后触发这些记录
可以看到多加了一条我自己的记录
- component被正确设置
- peer也被正确设置
- url被正确设置
- params都被正常设置
- body被正常设置