Skywalking源码分析【agent探针篇】

Skywalking agent源码分析

  • 字节码技术
  • 入口方法
  • 1、核心配置加载方式:
  • 2、插件初始化:
  • 3、插件(中间件or框架)的增强
    • 增强点的寻找:
  • 4、服务启动
  • 5、插件体系
    • 5.1、拦截实例方法:
    • 5.2、拦截构造方法:
    • 5.3、拦截静态方法
    • 5.4、对于实例方法和静态方法拦截点接口里的三个方法:
  • 6、witnessClass机制
  • 7、BootService 之 GRPCChanelManager
  • 9、ServiceAndEndPointRegisterClient
  • 10、trace数据采集
      • AbstractMethodInterceptor#beforeMethod
      • 核心是 span 的创建
      • createSpan为入栈过程,ContextManager.stopSpan()为出栈过程

字节码技术

xxxxx介绍一下字节码技术

  1. JavaAgent ,启动时加载的 JavaAgent 是 JDK1.5 之后引入的新特性,此特性为用户提供了在 JVM 将字节码文件读入内存之后,JVM 使用对应的字节流在 Java 堆中生成一个 Class 对象之前,用户可以对其字节码进行修改的能力,从而 JVM 也将会使用用户修改过之后的字节码进行 Class 对象的创建。
  2. premain api,
    public static void premain(String agentArgs, Instrumentation instrumentation);
    public static void premain(String agentArgs);

入口方法

SkyWalkingAgent -> premain。目前暂时只支持冷启动,热部署的方式仍需官方持续改进。

1、核心配置加载方式:

例如:agent.service_name 和 collector.backend_service等配置
读取 skywalking_config -> 若没有则读取 /config/agent.config -> System.getProperties() 系统级的环境变量 来覆盖前者-> 挂载的探针参数 来覆盖前者

2、插件初始化:

  1. AbstractClassEnhancePluginDefine 是所有插件必须继承的抽象类
  2. 每个插件下都有一个skywalking-plugin.def文件,以key(插件名)=value(完整包路径)的形式定义了插件的声明(即增强的切入点)

3、插件(中间件or框架)的增强

增强点的寻找:

  • NameMatch:是全路径找到增强点
  • IndirectMatch:通过类注解@Trace等、继承关系,回调结果等等信息来辅助匹配寻找增强点

4、服务启动

Config文件里的IS_OPEN_DEBUGGING_CLASS变量在二次开发时候在测试环境是可以打开的,发线上记得关闭。

5、插件体系

XXXInstrumentation类用于定义插件的拦截点(指定类的指定方法【实例方法,构造方法,静态方法】);

5.1、拦截实例方法:

	XXXInstrumentation extends ClassInstanceMethodsEnhancePluginDefine;
	重写 getInstanceMethodsInterceptPoints()来定义实例方法的拦截点
	XXXInteceptor implements InstanceMethodsAroundInterceptor
	重写 beforeMethod afterMethod handleMethodException 方法

5.2、拦截构造方法:

	XXXInstrumentation extends ClassInstanceMethodsEnhancePluginDefine;
	重写 getConstructorsInterceptPoints() 来定义构造方法拦截点
	XXXInteceptor implements InstanceConstructorInterceptor
	重写 onConstruct 方法

5.3、拦截静态方法

	XXXInstrumentation extends ClassStaticMethodsEnhancePluginDefine;	
	重写 getStaticMethodsInterceptPoints() 来定义静态方法的拦截点
	XXXInteceptor implements StaticMethodsAroundInterceptor
	重写 beforeMethod afterMethod handleMethodException 方法
	
	enhanceClass()是用于指定插件要拦截的类
	getConstructorsInterceptPoints()是指定要拦截的构造方法
	getInstanceMethodsInterceptPoints()是指定插件要拦截的实例方法

5.4、对于实例方法和静态方法拦截点接口里的三个方法:

		(1)getMethodsMatcher:定义要拦截的方法
		(2)getMethodsInterceptor:指定拦截器的全类名,这个拦截器是用于在拦截到指定方法后做具体操作的
		(3)isOverrideArgs:指定在拦截器工作的时候是否可以对原生方法的入参进行修改

6、witnessClass机制

在AbstractClassEnhancePluginDefine类中
方案:通过appClassLoader类加载器去寻找witnessClasses[]里写的全类名是否全部都真的存在(&的关系)来match对应的版本的插件,绕过插件的发行版本号获取不到从而无法匹配版本的难题

7、BootService 之 GRPCChanelManager

入口代码:ServiceManager.INSTANCE.boot();

9、ServiceAndEndPointRegisterClient

1、获取OAP分配给当前Agent所代表的的 Service 和 ServiceInstance 的ID => 注册 Service和ServiceInstance
2、ServiceInstance 和 OAP 保持心跳
3、同步 网络地址 和 Endpoint 

10、trace数据采集

注:以 spring-mvc-plugin 插件为例子,以下 源码来源于 agent 插件 mvc-annotation-commons,这个插件是官方自带的。

AbstractMethodInterceptor#beforeMethod

	代码:operationName = StringUtil.cut(operationName, OPERATION_NAME_THRESHOLD);就是页面上展示数据不齐全的原因
	     SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS 默认不打开,打开后就会去收集请求参数,限制1024长度提升性能

核心是 span 的创建

	1、ContextManager.createEntrySpan():
		如果在一个进程内这是第一个生成的span那么使用createEntrySpan()方法去创建,他除了会生成span还会帮你把之前短接的调用链给连接起来(比如 远程调用 A - > B 在 B服务 调用 createEntrySpan 才能和A 关联起来)
	2、ContextManager.createLocalSpan() : 
		本地 span, 最普通的创建 span 的方法
	3、ContextManager.createExitSpan() : 
		如果在一个进程内 发现这已经是这个进程最后一个调用 span, 使用createExitSpan 去创建对应的 span , 比如 使用 okhttp 去调用别的服务,那么在 okhttp 发送之前就已经是最后一个 span了,方法 createExitSpan 除了 会帮你创建一个 span,还会帮你把 一些 id 信息带给 被调用方(okhttp 是把ID信息给序列化放在 header里),被调用方使用 createEntrySpan() 就能把整个请求给 连接起来,这里需要留意的是 这里带给的 id 其实就是上问提到的 traceId/segmentId/spanId ,这三者组成了一个 完成的 refs 属性,

createSpan为入栈过程,ContextManager.stopSpan()为出栈过程

生产者---消费者  模型(因为发送这些span 数据不能够阻塞 我们的业务线程,且 有一定的数据量,需要批量发送)
	TracingContext#finish():去检查了一下存放span的栈空了都出栈了 才会执行后面的方法 . 换句话说就是,需要等同一个线程里面所有的span 都出栈了,才会去把这整个 segment 给放入 消费队列中。
	TracingContext.ListenerManager.notifyFinish(finishedSegment);消费的代码

	premian -> ServiceManager.INSTANCE.boot(); -> startup(); -> 所有BootService的子类都去service.boot(); -> TraceSegmentServiceClient#boot  执行初始化操作(创建消费队列、定义流式策略、定义消费者等)
	消费的核心代码:TraceSegmentServiceClient#consume(List data);  直接用的 GRPC 批量发送了所有的 span 数据。

你可能感兴趣的:(生态赋能,skywalking,源码分析,agent)