Java Agent本质上可以理解为一个插件,该插件就是一个特制的Jar包。这个Jar包通过JVMTI(JVM Tool Interface)完成加载,最终借助JPLISAgent(Java Programming Language Instrumentation Services Agent)完成对目标代码的修改
Java Agent
支持目标JVM启动时加载以及JVM运行时加载,这两种不同的加载模式会使用不同的入口函数。
如果需要在目标JVM启动的同时加载 Agent
,那么可以选择实现下面的方法
public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
如果需要在目标JVM运行时加载 Agent
,那么可以选择实现下面的方法
public static void agentmain(String agentArgs, Instrumentation inst)
public static void agentmain(String agentArgs)
JVM
将首先寻找1,如果没有发现1,再寻找2。代码中有两个参数需要注意
agentArgs:-javaagent 命令携带的参数。agent.service_name 这个配置项的默认值有三种覆盖方式,其中,使用探针配置进行覆盖,探针配置的值就是通过该参数传入的
inst:java.lang.instrumen.Instrumentation
是 Instrumention 包中定义的一个接口,它提供了操作类定义的相关方法,核心方法如下
定义一个 MANIFEST.MF
文件,在其中添加 premain-class
或 Agent-Class
配置项
Manifest-Version: 1.0
PreMain-Class: com.test.AgentClass
Agent-Class: com.test.AgentClass
com.sun.tools.attach.VirtualMachine
加载
skywalking的agent入口函数在org.apache.skywalking.apm.agent.SkywalkingAgent#premain
方法具体实现如下(省略了异常代码块及日志打印)
final PluginFinder pluginFinder;
// 初始化配置信息
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
// 加载agent插件, 并使用PluginFinder为插件进行分类
pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
// 创建一个ByteBuddy对象用于修改字节码
final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
// 创建一个AgentBuilder对象,详细代码省略...
AgentBuilder agentBuilder = new AgentBuilder(byteBuddy)...
// 使用JDK SPI加载并启动BootService
ServiceManager.INSTANCE.boot();
// 添加一个JVM勾子函数, 在JVM退出时关闭所有BootService服务
Runtime.getRuntime()
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
配置初始化的入口方法在
org.apache.skywalking.apm.agent.core.conf.SnifferConfigInitializer
#initializeCoreConfig
方法的具体实现如下(省略了异常代码块及日志打印)
AGENT_SETTINGS = new Properties();
// 1.加载agent.config配置文件
final InputStreamReader configFileStream = loadConfig();
AGENT_SETTINGS.load(configFileStream);
for (String key : AGENT_SETTINGS.stringPropertyNames()) {
String value = (String) AGENT_SETTINGS.get(key);
// 2.解析agent.config文件中的配置
AGENT_SETTINGS.put(key, PropertyPlaceholderHelper.INSTANCE.replacePlaceholders(value, AGENT_SETTINGS));
}
// 3.解析skywalking.开头的系统参数,截取后写入配置类
overrideConfigBySystemProp();
agentOptions = StringUtil.trim(agentOptions, ',');
if (!StringUtil.isEmpty(agentOptions)) {
agentOptions = agentOptions.trim();
// 4.解析Java Agent参数, 写入配置类
overrideConfigByAgentOptions(agentOptions);
}
// 5.将配置类中的信息填充到Config类对应的静态字段中
initializeConfig(Config.class);
IS_INIT_COMPLETED = true;
可以看到配置信息会依次从三个地方进行获取并覆盖
agent.config
文件javaagent options
所以,配置参数的优先级为 javaagent options > 系统参数 > agent.config文件
下面通过在3个地方同时配置 agent.service_name
参数来验证一下配置加载流程
以 Live-Demo + skywalking源码
项目为例
修改项目 ProjectB
的JVM启动参数,同时配置 javaagent options
、系统参数
-javaagent:/Users/wangbo/Documents/Study_Workspace/skywalking/skywalking-agent/skywalking-agent.jar=agent.service_name=Project_Options
-Dskywalking.agent.service_name=Project_Env
覆盖 agent.config
中指定参数的的环境变量
SW_AGENT_NAME=Project_Config
然后debug启动 projectB
进行源码调试
private static final String SPECIFIED_CONFIG_PATH = "skywalking_config";
private static final String DEFAULT_CONFIG_FILE_NAME = "/config/agent.config";
String specifiedConfigPath = System.getProperty(SPECIFIED_CONFIG_PATH);
File configFile = StringUtil.isEmpty(specifiedConfigPath) ? new File(
AgentPackagePath.getPath(), DEFAULT_CONFIG_FILE_NAME) : new File(specifiedConfigPath);
源码中第一步为加载 agent.config
配置文件,可以看出agent.config
文件加载的优先级为
环境变量 skywalking_config
指定的路径 > skywalking-agent.jar
同级的config目录
第一步agent.config文件加载到properties后,默认的配置格式都是 **配置项 = ${环境变量:配置默认值}**
第二步将对具体的值进行解析,首先判断环境变量中是否包含该配置,有则替换,无则使用默认值,所以解析后的配置格式变为 **配置项 = 配置值**
。可以看到 agent.service_name
对应的值为Project_Config
第三步为解析系统参数,可以看到 agent.service_name
被覆盖为 Project_Env
第四步为解析javaagent options参数,可以看到agent.service_name
被覆盖为Project_Options
配置加载的最终目的就是将配置项全部填充到Config配置类的各个静态字段中,这样后续使用配置信息时直接通过Config类进行获取即可,可以看到之前配置的 agent.service_name
在最后一步被确定为 Project_Options
加载插件的入口方法在
org.apache.skywalking.apm.agent.core.plugin.PluginBootstrap#loadPlugins
方法的具体实现如下
// 自定义类加载器初始化
AgentClassLoader.initDefaultLoader();
// 创建插件定义解析类
PluginResourcesResolver resolver = new PluginResourcesResolver();
// 通过调用AgentClassLoader的资源加载方法解析出所有插件定义文件的路径
List<URL> resources = resolver.getResources();
if (resources == null || resources.size() == 0) {
return new ArrayList<AbstractClassEnhancePluginDefine>();
}
// 将插件定义文件解析为插件配置类
for (URL pluginUrl : resources) {
PluginCfg.INSTANCE.load(pluginUrl.openStream());
}
List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();
List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();
for (PluginDefine pluginDefine : pluginClassList) {
// 通过反射将所有插件定义类实例化
AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader
.getDefault()).newInstance();
plugins.add(plugin);
}
// 通过SPI的方式发现并实例化其他插件并添加到plugins中
plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault()));
由于应用默认的类加载器只能加载JVM相关的类库及 Classpath
下的类库,skywalking
为了实现不修改应用结构,无侵入加载插件jar包,自定义了一个 AgentClassLoader
AgentClassLoader
的静态代码块会调用 registerAsParallelCapable()
方法开启并行类加载功能,一方面能优化类加载的速度,另一方面是为了解决JVM7以上版本的类加载器死锁问题
https://github.com/apache/skywalking/pull/2016
旧版的 AgentClassLoader
未开启并行机制,默认的loadClass方法会使用 synchronized
锁住当前的类加载器对象,当 WebAppClassLoader
与 AgentClassLoader
加载的类有依赖关系时就会产生死锁。而开启了并行机制后,loadClass
会将锁的级别降低到被加载的类的级别上,不再对类加载器进行加锁
PS:当委派模型没有严格分层的环境中(如出现闭环委托),类加载器需要具有并行能力,否则会导致死锁
我们知道agent的插件目录有四种类型
bootstrap-plugins
目录下plugins
目录下optional-plugins
目录下activations
目录下再通过查看 AgentClassLoader
的构造函数可以发现默认只加载plugins
和 activations
路径下的插件
// Config.Plugin.MOUNT = Arrays.asList("plugins", "activations");
public AgentClassLoader(ClassLoader parent) throws AgentPackageNotFoundException {
super(parent);
File agentDictionary = AgentPackagePath.getPath();
classpath = new LinkedList<>();
Config.Plugin.MOUNT.forEach(mountFolder -> classpath.add(new File(agentDictionary, mountFolder)));
}
PluginResourcesResolver
是Agent插件的资源解析器,会通过 AgentClassLoader
中的
findResource()
方法读取所有Agent插件中的 skywalking-plugin.def
文件
以activemq-5.x-plugin插件为例
activemq-5.x=org.apache.skywalking.apm.plugin.activemq.define.ActiveMQProducerInstrumentation
activemq-5.x=org.apache.skywalking.apm.plugin.activemq.define.ActiveMQConsumerInstrumentation
拿到所有 def
文件后,会使用 PluginCfg
类将def中的定义转换为 PluginDefine
对象,该对象有两个字段
public class PluginDefine {
// 插件名称:activemq-5.x
private String name;
// 插件定义类的路径:org.apache.skywalking.apm.plugin.activemq.define.ActiveMQProducerInstrumentation
private String defineClass;
}
然后遍历所有 PluginDefine
对象,通过反射 defineClass
字段将每一个插件实例化为
AbstractClassEnhancePluginDefine
对象,添加到plugins列表中,最后将SPI发现并实例化其他插件也添加到plugins列表中返回
该类是所有 Agent
插件类的顶级父类,通过官方的 插件开发指南 可以得知,SkyWalking
提供两类通用的定义去拦截构造方法, 实例方法和类方法
ClassInstanceMethodsEnhancePluginDefine
定义了构造方法 Contructor
拦截点和 instance method
实例方法拦截点.
ClassStaticMethodsEnhancePluginDefine
定义了类方法 class method
拦截点
也可以继承 ClassEnhancePluginDefine
去设置所有的拦截点, 但这不常用
AbstractClassEnhancePluginDefine
抽象出了所有实现类增强时需要调用的方法
ClassEnhancePluginDefine
定义了所有抽象方法的具体执行步骤
然后真正的增强逻辑全部通过实现 ClassInstanceMethodsEnhancePluginDefine
和
ClassStaticMethodsEnhancePluginDefine
中的抽象方法延迟到子类中
其中的核心方法定义
define()
:插件类增强逻辑的入口,定义了如何拦截需要修改的 Java 类enhance()
:真正调用byte buddy执行增强逻辑的地方enhanceClass()
:返回 ClassMatch,用于匹配当前插件要增强的目标类witnessClass()
:一个开源组件可能有多个版本,插件会通过该方法识别组件的不同版本,防止对不兼容的版本进行增强getConstructorsInterceptPoints()
:获取 Contructor
构造器拦截点列表getInstanceMethodsInterceptPoints()
:获取 instance method
实例方法拦截点列表getStaticMethodsInterceptPoints()
:获取 class method
类方法拦截点列表插件加载方法 loadPlugins()
执行结束后返回了已经实例化的 AbstractClassEnhancePluginDefine
插件定义类列表,然后传入了 PluginFinder
的构造函数中
org.apache.skywalking.apm.agent.core.plugin.PluginFinder#PluginFinder()
// 使用NameMatch的插件定义实例列表
private final Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<String, LinkedList<AbstractClassEnhancePluginDefine>>();
// 使用IndirectMatch的插件定义实例列表
private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
// 引导类插件定义实例列表
private final List<AbstractClassEnhancePluginDefine> bootstrapClassMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
public PluginFinder(List<AbstractClassEnhancePluginDefine> plugins) {
for (AbstractClassEnhancePluginDefine plugin : plugins) {
// 获取当前插件定义类的ClassMatch
ClassMatch match = plugin.enhanceClass();
if (match == null) {
continue;
}
// 如果匹配规则为名称匹配
if (match instanceof NameMatch) {
NameMatch nameMatch = (NameMatch) match;
LinkedList<AbstractClassEnhancePluginDefine> pluginDefines = nameMatchDefine.get(nameMatch.getClassName());
if (pluginDefines == null) {
pluginDefines = new LinkedList<AbstractClassEnhancePluginDefine>();
// 以类名为key, 类的实例对象为value写入nameMatchDefine集合中
nameMatchDefine.put(nameMatch.getClassName(), pluginDefines);
}
pluginDefines.add(plugin);
} else {
// 如果不是名称匹配, 写入signatureMatchDefine集合中
signatureMatchDefine.add(plugin);
}
// 引导类插件写入bootstrapClassMatchDefine集合中
if (plugin.isBootstrapInstrumentation()) {
bootstrapClassMatchDefine.add(plugin);
}
}
}
enhanceClass()
方法根据返回的 ClassMatch
类型进行分类,写入不同的集合中,并在后面 Byte Buddy
匹配需要增强的类时传入匹配规则
根据官方的 插件开发指南 得知,常见的类匹配有四种方式
byName
通过类的全限定名(Fully Qualified Class Name, 即 包名 + . + 类名).byClassAnnotationMatch
根据目标类是否存在某些注解.byMethodAnnotationMatch
根据目标类的方法是否存在某些注解.byHierarchyMatch
根据目标类的父类或接口对应的实现类如下
NameMatch
:直接使用完整类名进行匹配IndirectMatch
:间接规则匹配接口
ClassAnnotationMatch
:基于类注解进行匹配,可设置同时匹配多个。例如:@RequestMapping
HierarchyMatch
:基于父类 / 接口进行匹配,可设置同时匹配多个。MethodAnnotationMatch
:基于方法注解进行匹配,可设置同时匹配多个。目前项目里主要用于匹配方法上的 @Trace
注解综上,PluginFinder
是持有了所有插件定义对象的一个容器,并对不同插件进行分类存放在了不同的集合中
AgentBuilder
是 Byte Buddy
库专门用来支持 Java Agent
的一个 API
AgentBuilder
相关的核心代码如下
// 创建一个ByteBuddy对象用于修改字节码
final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
// 创建一个AgentBuilder对象
AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy)
// 忽略不需要通过插件增强的类
.ignore(
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("org.slf4j."))
.or(nameStartsWith("org.groovy."))
.or(nameContains("javassist"))
.or(nameContains(".asm."))
.or(nameContains(".reflectasm."))
.or(nameStartsWith("sun.reflect"))
.or(allSkyWalkingAgentExcludeToolkit())
.or(ElementMatchers.isSynthetic())
)
// 省略部分代码 ... ...
// 指定需要通过插件增强的类
.type(pluginFinder.buildMatch())
// 设置transform(具体的增强逻辑)
.transform(new Transformer(pluginFinder))
// 设置已加载类的agent修改策略
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
//回调接口,用来在debug模式下保存增强后的字节码文件
.with(new Listener())
.installOn(instrumentation);
再来看一下匹配规则的具体逻辑,方法入口
org.apache.skywalking.apm.agent.core.plugin.PluginFinder#buildMatch
// 使用NameMatch的插件定义实例列表
private final Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<String, LinkedList<AbstractClassEnhancePluginDefine>>();
// 使用IndirectMatch的插件定义实例列表
private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
public ElementMatcher<? super TypeDescription> buildMatch() {
// 建立匹配规则, 在内部类中添加第一个默认规则
ElementMatcher.Junction judge = new AbstractJunction<NamedElement>() {
@Override
public boolean matches(NamedElement target) {
// 名称匹配模式的插件全部使用类名进行匹配
return nameMatchDefine.containsKey(target.getActualName());
}
};
// 排除所有的接口
judge = judge.and(not(isInterface()));
// 遍历特殊匹配规则模式的插件列表
for (AbstractClassEnhancePluginDefine define : signatureMatchDefine) {
ClassMatch match = define.enhanceClass();
if (match instanceof IndirectMatch) {
// 获取特殊匹配规则
judge = judge.or(((IndirectMatch) match).buildJunction());
}
}
// 返回ByteBuddy元素匹配器
return new ProtectiveShieldMatcher(judge);
}
AgentBuilder
的 transform()
方法需要传入一个 Transformer
类,该类需要再实现一个
transform()
方法,每当有一个类加载时,就会触发 transform
逻辑,对类进行匹配和修改,所以这个
Transformer
类就是真正触发 ByteBuddy
增强逻辑的入口,我们看一下它的实现
private static class Transformer implements AgentBuilder.Transformer {
private PluginFinder pluginFinder;
Transformer(PluginFinder pluginFinder) {
this.pluginFinder = pluginFinder;
}
@Override
public DynamicType.Builder<?> transform(final DynamicType.Builder<?> builder,
final TypeDescription typeDescription,
final ClassLoader classLoader,
final JavaModule module) {
// typeDescription 是要被修改类的完整描述,pluginFinder的find方法可以根据描述类的类名或其他信息取出插件定义类实例
List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription);
if (pluginDefines.size() > 0) {
DynamicType.Builder<?> newBuilder = builder;
// EnhanceContext 是为了保证流程的一个记录器, 比如执行了某个步骤后就会记录一下, 防止重复操作
EnhanceContext context = new EnhanceContext();
for (AbstractClassEnhancePluginDefine define : pluginDefines) {
// 调用插件定义类的 define 方法真正执行修改字节码的逻辑
DynamicType.Builder<?> possibleNewBuilder = define.define(
typeDescription, newBuilder, classLoader, context);
if (possibleNewBuilder != null) {
newBuilder = possibleNewBuilder;
}
}
if (context.isEnhanced()) {
LOGGER.debug("Finish the prepare stage for {}.", typeDescription.getName());
}
// 返回修改后的字节码, 进行替换
return newBuilder;
}
LOGGER.debug("Matched class {}, but ignore by finding mechanism.", typeDescription.getTypeName());
return builder;
}
}
Skywalking Agent
采用了插件化架构(又称微内核架构),其中 apm-agent-core
对应的就是内核系统,而
apm-sdk-plugin
对应的就是各个插件模块。前面的内容我们讲解了插件是如何被加载的,那么插件又是如何与 OAP
通信的?配置又是如何拉取的?这些功能服务就是封装在各个 BootService
当中
首先所有的插件服务都需要实现接口 org.apache.skywalking.apm.agent.core.boot.BootService
其中定义了4个生命周期的方法
public interface BootService {
void prepare() throws Throwable;
void boot() throws Throwable;
void onComplete() throws Throwable;
void shutdown() throws Throwable;
}
其次每个 BootService
实现还需要使用到两种注解
@DefaultImplementor
用于标识 BootService 接口的默认实现@OverrideImplementor
用于覆盖默认 BootService 实现,通过其 value 字段指定要覆盖的默认实现并且覆盖实现只能覆盖默认实现,不能再去覆盖另外的覆盖实现
GRPCChannelManager
:维护 Agent 与后端 OAP 集群通信时使用的网络连接JVMService
:收集并发送服务实例的 CPU、堆内存使用情况以及 GC 信息ContextManager
:负责管理一个 SkyWalking Agent 中所有的 Context 对象ContextManagerExtendService
:负责创建 Context 对象TraceSegmentServiceClient
:负责将 Trace 数据序列化并发送到 OAP 集群SamplingService
:负责实现 Trace 的采样ConfigurationDiscoveryService
:检测动态配置并更新再来看看启动boot服务的核心方法入口在 org.apache.skywalking.apm.agent.core.boot.ServiceManager
它是一个使用枚举实现单例模式的工具类,存放所有 BootService
的实例并管理其生命周期
// BootService实例容器,使用Map结构方便服务间直接通过Class对象进行调用
private Map<Class, BootService> bootedServices = Collections.emptyMap();
public void boot() {
// 加载所有 BootService 的实现并写入实例容器
bootedServices = loadAllServices();
// 循环调用所有boot服务的prepare方法
prepare();
// 循环调用所有boot服务的boot方法
startup();
// 循环调用所有boot服务的onComplete方法
onComplete();
}
private Map<Class, BootService> loadAllServices() {
Map<Class, BootService> bootedServices = new LinkedHashMap<>();
List<BootService> allServices = new LinkedList<>();
// 加载出所有 BootService 实现
load(allServices);
for (final BootService bootService : allServices) {
Class<? extends BootService> bootServiceClass = bootService.getClass();
// 首先判断这个 BootService 服务是否有 DefaultImplementor 注解
boolean isDefaultImplementor = bootServiceClass.isAnnotationPresent(DefaultImplementor.class);
if (isDefaultImplementor) {
// 判断这个 BootService 是否被加载过
if (!bootedServices.containsKey(bootServiceClass)) {
// 如果没有加载过则进行加载
bootedServices.put(bootServiceClass, bootService);
} else {
//ignore the default service
}
} else {
// 判断这个 BootService是否有 OverrideImplementor 注解
OverrideImplementor overrideImplementor = bootServiceClass.getAnnotation(OverrideImplementor.class);
if (overrideImplementor == null) {
// 如果这个 BootService 既没有 DefaultImplementor 注解,又没有 OverrideImplementor 注解
// 判断这个 BootService 是否被加载过
if (!bootedServices.containsKey(bootServiceClass)) {
// 没有的话就当作是默认实现进行加载
bootedServices.put(bootServiceClass, bootService);
} else {
// 如果被加载过说明这个 BootService 包含多个不一致的定义,则抛出异常
throw new ServiceConflictException("Duplicate service define for :" + bootServiceClass);
}
} else {
// 取出覆盖实现需要覆盖的 BootService Class 类
Class<? extends BootService> targetService = overrideImplementor.value();
// 判断这个 BootService 是否被加载过
if (bootedServices.containsKey(targetService)) {
// 如果被加载过,则判断这个 BootService 是否有 DefaultImplementor 注解
boolean presentDefault = bootedServices.get(targetService)
.getClass()
.isAnnotationPresent(DefaultImplementor.class);
if (presentDefault) {
// 如果有直接进行覆盖
bootedServices.put(targetService, bootService);
} else {
// 没有则说明覆盖实现覆盖了另一个覆盖实现,不符合规则定义,抛出异常
throw new ServiceConflictException(
"Service " + bootServiceClass + " overrides conflict, " + "exist more than one service want to override :" + targetService);
}
} else {
// 如果没有被加载过,则直接进行覆盖
bootedServices.put(targetService, bootService);
}
}
}
}
return bootedServices;
}
void load(List<BootService> allServices) {
// 通过SPI机制从 META-INF/services 文件下加载出所有bootservice接口的实现
for (final BootService bootService : ServiceLoader.load(BootService.class, AgentClassLoader.getDefault())) {
allServices.add(bootService);
}
}
加载流程结束后会依次调用所有 BootService
实例的 prepare()
、 boot()
、 onComplete()
方法
这些服务全部启动起来之后,Agent
才能真正的对外提供服务
trace-ignore-plugin/
├── apm-trace-ignore-plugin.config
├── apm-trace-ignore-plugin.iml
├── pom.xml
└── src
└── main
├── java/org/apache/skywalking/apm/plugin/trace
│ └── ignore
│ ├── TraceIgnoreExtendService.java # BootService覆盖实现类, 覆盖SamplingService
│ ├── TraceIgnorePatternWatcher.java
│ └── conf
│ ├── IgnoreConfig.java
│ │ └── IgnoreConfigInitializer.java
│ └── matcher
│ ├── FastPathMatcher.java
│ └── TracePathMatcher.java
└── resources
└── META-INF
└── services
└── org.apache.skywalking.apm.agent.core.boot.BootService # SPI增加BootService实现
该插件主要扩展了 Trace
的采样实现 SamplingService
我们先简单了解一下 Trace
收集和发送时涉及的服务
TracingContext
保存了 Trace
的上下文信息,ContextManagerExtendService
则负责创建
TracingContext
,并且会调用 SamplingService
的 trySampling()
方法进行采样,如果采样失败(例如配置了 slow Trace
采样时间阈值),这时 ContextManagerExtendService
就会生成
IgnoredTracerContext
,它是个空 Context 实现,不会记录 Trace
信息
而 TraceIgnoreExtendService
这个覆盖实现重写了 SamplingService
的 trySampling()
方法
它通过匹配我们配置的 ignore_path
,直接忽略了 Trace
信息的生成
@Override
public boolean trySampling(final String operationName) {
if (patterns.length > 0) {
for (String pattern : patterns) {
if (pathMatcher.match(pattern, operationName)) {
LOGGER.debug("operationName : " + operationName + " Ignore tracking");
return false;
}
}
}
return super.trySampling(operationName);
}
endpoint
endpoint
将会不会被追踪Live-Demo&Skywalking源码工程
apm-trace-ignore-plugin.jar
文件从 /optional-plugins
目录copy到 /plugins
目录skywalking.trace.ignore_path
值是要忽略的路径修改 ProjectB
项目的唯一接口,让其直接返回 Trace ID
然后启动 ProjectB项目 并配置系统环境变量 skywalking.trace.ignore_path
-Dskywalking.trace.ignore_path=/projectB/test
打开浏览器访问 localhost:8762/projectB/test 可以看到直接返回了 Ignore Trace
而访问其他path则正常显示 Trace ID
启动流程的最后,会添加一个JVM钩子函数,当JVM退出时执行,方法入口在 org.apache.skywalking.apm.agent.core.boot.ServiceManager#shutdown
public void shutdown() {
for (BootService service : bootedServices.values()) {
try {
service.shutdown();
} catch (Throwable e) {
LOGGER.error(e, "ServiceManager try to shutdown [{}] fail.", service.getClass().getName());
}
}
}
它会遍历所有的 BootService
实例并执行 shutdown()
方法来停止所有服务
本文通过对 Skywalking Agent
核心启动流程的介绍,让我们从大体上了解到探针在我们的应用启动之前究竟做了哪些准备工作。主要包括配置的加载、插件的加载、ByteBuddy
如何增强目标类以及 BootService
的加载。知道了这些之后,能够为我们接下来更深入的研究 Agent
的各种机制和原理时做好铺垫。