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. 编写带有特定方法的类
    1
    public static void premain(String agentArgs, Instrumentation inst){}
  2. 打包成jar,并且添加manifest属性Premain-Class: pers.apricot.MyAgent
  3. 应用添加-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提供了两类通用的定义去拦截构造方法,实例方法和静态方法

  1. ClassInstanceMethodsEnhancePluginDefine定义了Contructorinstance method实例方法拦截点
  2. ClassStaticMethodsEnhancePluginDefine定义了class method拦截点
  3. 继承ClassEnhancePluginDefine去设置所有的拦截点,不常用

匹配目标类

定义ClassInstanceMethodsEnhancePluginDefine的子类,重写enhanceClass去匹配目标类.

ClassMatchClassMatch表示如何去匹配目标类,这里有四种方法

  1. byName, 通过类的全限定名(Fully Qualified Class Name, 即 包名 + . + 类名).
  2. byClassAnnotationMatch, 根据目标类是否存在某些注解.
  3. byMethodAnnotationMatch, 根据目标类的方法是否存在某些注解.
  4. byHierarchyMatch, 根据目标类的父类或接口

注意事项:

  1. 禁止使用 .class.getName() 去获取类名, 建议你使用文本字符串, 这是为了避免 ClassLoader 的问题.byAnnotationMatch 不支持从父类继承来的注解.
  2. 除非确实必要, 否则不建议使用 byHierarchyMatch, 因为使用它可能会触发拦截许多预期之外的方法, 会导致性能问题和不稳定.

定义方法拦截

重新getInstanceMethodsInterceptPoints方法

  1. 重新方法
  2. 定义拦截方法的几种逻辑
  3. 方法匹配
  4. 放回方法拦截器的全类名,这个ElementMatcherbyteBuddy的内容,所以,skyWalking这块的实现直接套用了byteBuddy的东西,省了一些代码
  5. 是否覆盖参数,如果你需要在拦截器中更改引用的参数,就需要返回true,修改InstanceMethodsAroundInterceptorallArguments才起效

skywalking-plugin.def 添加定义

key是语义化的名字即可,然后后面是Instrumentation的全限定类名,也就是定义拦截的类,拦截的方法,拦截的逻辑,这三个要素的类

1
key=com.any.SomeClass

实现拦截器

  1. 接口名称
  2. 方法执行前,获取该对象,该方法,传的参数,参数类型,方法拦截结果
  3. 方法执行后,返回的结果,这里可以返回自定义的结果,也s就是返回代理对象

skyWalking jdbc 插件解析

  1. 定义拦截的类
  2. 定义拦截的方法
  1. 定义拦截的逻辑,创建span
  2. 设定cmponent
  3. 设定layer
  1. 添加定义

自定义开发组件

在项目中,我使用了RestTemplate和外部的接口交互,获取数据,假定我想记录我发送了多少次请求,每次请求的内容是什么,返回的内容是什么,以这个作为需求来
开发一个skyWalking的插件

拦截的类和方法

我主要就用了这一个方法,名字是postForEntity,有三个重载的方法,但是我只拦截这一个

Instrumentation编码

  1. 拦截的类名
  2. 拦截的方法,需要按照需要精确定义
  3. 拦截器

Interceptor 编码

我根据需求,根据入参和返回的参数,获取我需要数据,完成了http发送请求请求参数和返回数据的获取

  1. 创建ContextCarrier
  2. 定义component
  3. 定义layer
  4. 设置params
  5. 设置body

skywalking-plugin.def

根据规范设置Instrumentation

查询效果

  1. 我首先打包这个jar
  2. 然后放到plugins目录
  3. 依次启动服务,然后触发这些记录

可以看到多加了一条我自己的记录

  1. component被正确设置
  2. peer也被正确设置
  3. url被正确设置
  4. params都被正常设置
  5. body被正常设置