xxxxx介绍一下字节码技术
public static void premain(String agentArgs, Instrumentation instrumentation);
public static void premain(String agentArgs);
SkyWalkingAgent -> premain。目前暂时只支持冷启动,热部署的方式仍需官方持续改进。
例如:agent.service_name 和 collector.backend_service等配置
读取 skywalking_config -> 若没有则读取 /config/agent.config -> System.getProperties() 系统级的环境变量 来覆盖前者-> 挂载的探针参数 来覆盖前者
Config文件里的IS_OPEN_DEBUGGING_CLASS变量在二次开发时候在测试环境是可以打开的,发线上记得关闭。
XXXInstrumentation类用于定义插件的拦截点(指定类的指定方法【实例方法,构造方法,静态方法】);
XXXInstrumentation extends ClassInstanceMethodsEnhancePluginDefine;
重写 getInstanceMethodsInterceptPoints()来定义实例方法的拦截点
XXXInteceptor implements InstanceMethodsAroundInterceptor
重写 beforeMethod afterMethod handleMethodException 方法
XXXInstrumentation extends ClassInstanceMethodsEnhancePluginDefine;
重写 getConstructorsInterceptPoints() 来定义构造方法拦截点
XXXInteceptor implements InstanceConstructorInterceptor
重写 onConstruct 方法
XXXInstrumentation extends ClassStaticMethodsEnhancePluginDefine;
重写 getStaticMethodsInterceptPoints() 来定义静态方法的拦截点
XXXInteceptor implements StaticMethodsAroundInterceptor
重写 beforeMethod afterMethod handleMethodException 方法
enhanceClass()是用于指定插件要拦截的类
getConstructorsInterceptPoints()是指定要拦截的构造方法
getInstanceMethodsInterceptPoints()是指定插件要拦截的实例方法
(1)getMethodsMatcher:定义要拦截的方法
(2)getMethodsInterceptor:指定拦截器的全类名,这个拦截器是用于在拦截到指定方法后做具体操作的
(3)isOverrideArgs:指定在拦截器工作的时候是否可以对原生方法的入参进行修改
在AbstractClassEnhancePluginDefine类中
方案:通过appClassLoader类加载器去寻找witnessClasses[]里写的全类名是否全部都真的存在(&的关系)来match对应的版本的插件,绕过插件的发行版本号获取不到从而无法匹配版本的难题
入口代码:ServiceManager.INSTANCE.boot();
1、获取OAP分配给当前Agent所代表的的 Service 和 ServiceInstance 的ID => 注册 Service和ServiceInstance
2、ServiceInstance 和 OAP 保持心跳
3、同步 网络地址 和 Endpoint
注:以 spring-mvc-plugin 插件为例子,以下 源码来源于 agent 插件 mvc-annotation-commons,这个插件是官方自带的。
代码:operationName = StringUtil.cut(operationName, OPERATION_NAME_THRESHOLD);就是页面上展示数据不齐全的原因
SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS 默认不打开,打开后就会去收集请求参数,限制1024长度提升性能
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 属性,
生产者---消费者 模型(因为发送这些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 数据。