零维护

 找回密码
 立即注册
快捷导航
搜索
热搜: 活动 交友 discuz
查看: 104|回复: 1

Skywalking8.9.1源码解析<八>-Skywalking-Agent启动流程

[复制链接]

1

主题

5

帖子

9

积分

新手上路

Rank: 1

积分
9
发表于 2022-9-22 02:49:42 | 显示全部楼层 |阅读模式

  • 写在开篇:本系列文章中,有自己的体会,有网上借鉴,如有雷同,请联系本人删除,莫要举报,本人无收益,纯属记录。若再次被封禁,将不再解封。
概览

Skywalking的原理是java-agent,所以整个核心的启动方法也就是premain方法,主要执行流程如下


代码解析:
/**
* Main entrance. Use byte-buddy transform to enhance all classes, which define in plugins.
* agentArgs: -javaagent:/path/to/agent.jar=agentArgs,配置参数后得参数
* Instrumentation:插庄服务的接口,在计算机科学技术中的英文释义是插桩、植入
*
* 开发者可以构建一个独立于应用程序的代理程序(Agent),用来检测和协助运行在JVM上的程序,甚至可以替换和修改某些类上的定义,
* 这样的特性实际上提供了一种虚拟机级别的支持AOP的方式,是的开发者对原有应用不做任何改变,就可以实现动态修改和增强
*
*/
public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {

    // 这里面的每一行代码都会在,一个服务运行的时候重新运行一次
    final PluginFinder pluginFinder;
    try {
        // 1、初始化配置信息,加载agent.config配置文件,其中会检测Java Agent参数以及环境变量是否覆盖了相应配置
        SnifferConfigInitializer.initializeCoreConfig(agentArgs);
    } catch (Exception e) {
        // 这里需要重新加载日志配置
        LogManager.getLogger(SkyWalkingAgent.class)
                .error(e, &amp;#34;SkyWalking agent initialized failure. Shutting down.&amp;#34;);
        return;
    } finally {
        // refresh logger again after initialization finishes
        LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
    }

    try {
        // 2、AgentClassLoader 加载插件类并进行实例化: PluginFinder提供插件匹配功能,使用自定义类加载器加载插件,这样就可以让应用无依赖无感知的插件,加载所有插件
        pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
    } catch (AgentPackageNotFoundException ape) {
        LOGGER.error(ape, &amp;#34;Locate agent.jar failure. Shutting down.&amp;#34;);
        return;
    } catch (Exception e) {
        LOGGER.error(e, &amp;#34;SkyWalking agent initialized failure. Shutting down.&amp;#34;);
        return;
    }
    // 3、使用ByteBuddy 创建AgentBuilder,使用ByteBuddy来进行字节码增强
    final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
    // 指定bytebuddy忽略的类
    AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore(
            nameStartsWith(&amp;#34;net.bytebuddy.&amp;#34;)
                    .or(nameStartsWith(&amp;#34;org.slf4j.&amp;#34;))
                    .or(nameStartsWith(&amp;#34;org.groovy.&amp;#34;))
                    .or(nameContains(&amp;#34;javassist&amp;#34;))
                    .or(nameContains(&amp;#34;.asm.&amp;#34;))
                    .or(nameContains(&amp;#34;.reflectasm.&amp;#34;))
                    .or(nameStartsWith(&amp;#34;sun.reflect&amp;#34;))
                    .or(allSkyWalkingAgentExcludeToolkit())
                    .or(ElementMatchers.isSynthetic())); // java编译器编译的时候生成的一些类,这些类找不到源代码
    JDK9ModuleExporter.EdgeClasses edgeClasses = new JDK9ModuleExporter.EdgeClasses();
    try {
        // 注入到BootStrapClassLoader
        agentBuilder = BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses);
    } catch (Exception e) {
        LOGGER.error(e, &amp;#34;SkyWalking agent inject bootstrap instrumentation failure. Shutting down.&amp;#34;);
        return;
    }

    try {
        //解决JDK9模块问题
        agentBuilder = JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses);
    } catch (Exception e) {
        LOGGER.error(e, &amp;#34;SkyWalking agent open read edge in JDK 9+ failure. Shutting down.&amp;#34;);
        return;
    }
    // 是否把修改的字节码保存一份。默认不加
    if (Config.Agent.IS_CACHE_ENHANCED_CLASS) {
        try {
            agentBuilder = agentBuilder.with(new CacheableTransformerDecorator(Config.Agent.CLASS_CACHE_MODE));
            LOGGER.info(&amp;#34;SkyWalking agent class cache [{}] activated.&amp;#34;, Config.Agent.CLASS_CACHE_MODE);
        } catch (Exception e) {
            LOGGER.error(e, &amp;#34;SkyWalking agent can&amp;#39;t active class cache.&amp;#34;);
        }
    }
    // 指定ByteBuddy要拦截的类
    agentBuilder.type(pluginFinder.buildMatch())
                .transform(new Transformer(pluginFinder))// 插庄,字节码增强,改字节码
                // 重定义与retransform的区别就是是否保留之前存在的内容
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .with(new RedefinitionListener())
                .with(new Listener())
                .installOn(instrumentation);

    try {
        // 4、采用Spi加载方式并启动BootService服务
        ServiceManager.INSTANCE.boot();
    } catch (Exception e) {
        LOGGER.error(e, &amp;#34;Skywalking agent boot failure.&amp;#34;);
    }
    // 5、添加JVM钩子
    Runtime.getRuntime()
            .addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, &amp;#34;skywalking service shutdown thread&amp;#34;));
}
1、初始化配置

入口:#SnifferConfigInitializer.initializeCoreConfig(agentArgs);
作用:初始化配置信息,如下图所示



  • 加载配置信息
AGENT_SETTINGS = new Properties();
// 通过loadConfig()方法加载配置信息,优先级最低
try (final InputStreamReader configFileStream = loadConfig()) {
    AGENT_SETTINGS.load(configFileStream);
    for (String key : AGENT_SETTINGS.stringPropertyNames()) {
        String value = (String) AGENT_SETTINGS.get(key);
        AGENT_SETTINGS.put(key, PropertyPlaceholderHelper.INSTANCE.replacePlaceholders(value, AGENT_SETTINGS));
    }

} catch (Exception e) {
    LOGGER.error(e, &amp;#34;Failed to read the config file, skywalking is going to run in default config.&amp;#34;);
}

try {
    // 用系统环境变量参数覆盖上述配置文件中的配置,优先级第二
    overrideConfigBySystemProp();
} catch (Exception e) {
    LOGGER.error(e, &amp;#34;Failed to read the system properties.&amp;#34;);
}

agentOptions = StringUtil.trim(agentOptions, &amp;#39;,&amp;#39;);
if (!StringUtil.isEmpty(agentOptions)) {
    try {
        agentOptions = agentOptions.trim();
        LOGGER.info(&amp;#34;Agent options is {}.&amp;#34;, agentOptions);
        //-javaagent:/path/to/agent.jar=agentArgs,中的参数,优先级最高,会覆盖上面配置
        overrideConfigByAgentOptions(agentOptions);
    } catch (Exception e) {
        LOGGER.error(e, &amp;#34;Failed to parse the agent options, val is {}.&amp;#34;, agentOptions);
    }
}

  • 将配置信息映射到org.apache.skywalking.apm.agent.core.conf#Config类中的属性 入口:#initializeConfig(Config.class);
public static void initializeConfig(Class configClass) {
    if (AGENT_SETTINGS == null) {
        LOGGER.error(&amp;#34;Plugin configs have to be initialized after core config initialization.&amp;#34;);
        return;
    }
    try {
        // 会将最终的配置信息填充到Config的静态字段中,将agent.config文件中全部配置信息填充到config中
        ConfigInitializer.initialize(AGENT_SETTINGS, configClass);
    } catch (IllegalAccessException e) {
        LOGGER.error(e,
                     &amp;#34;Failed to set the agent settings {}&amp;#34;
                         + &amp;#34; to Config={} &amp;#34;,
                     AGENT_SETTINGS, configClass
        );
    }
}

  • 初始化日志,根据配置重新选择指定日志解释器(如果基于Skywalking进行开发,不会用到它们的日志,所以这块可以裁剪)
  • 检测探针名称、服务器地址信息是否配置
if (StringUtil.isEmpty(Config.Agent.SERVICE_NAME)) {
    throw new ExceptionInInitializerError(&amp;#34;`agent.service_name` is missing.&amp;#34;);
}
if (StringUtil.isEmpty(Config.Collector.BACKEND_SERVICE)) {
    throw new Excep
//标记完成配置初始化
IS_INIT_COMPLETED = true;
2、加载插件

方法入口:#org.apache.skywalking.apm.agent.core.plugin.PluginBootstrap#loadPlugins()



  • 初始化自定义类加载器
// 初始化自定义加载器,该方法中会选择合适的类加载器,并示例话AgentClassLoader加载指定路径下的插件
AgentClassLoader.initDefaultLoader();
初始化方法入口:#org.apache.skywalking.apm.agent.core.plugin.loader.AgnetClassLoader 其中静态代码块中调用了registerAsParallelCapable()方法,开启并行类加载功能。
旧版的 AgentClassLoader 未开启并行机制,默认的loadClass方法会使用 synchronized 锁住当前的类加载器对象,当 WebAppClassLoader 与 AgentClassLoader 加载的类有依赖关系时就会产生死锁。而开启了并行机制后,loadClass 会将锁的级别降低到被加载的类的级别上,不再对类加载器进行加锁
static {
    /*
     * Try to solve the classloader dead lock. See https://github.com/apache/skywalking/pull/2016
     * 为什么要用类加载器?之所以自定义类加载器,是因为应用默认的类加载器只能加载JVM相关的类库及Classpath下的类库,Skywalking为了实现不修改应用结构,无侵入加载jar包,不在应用的classpath中引入Skywalking的插件jar包。这样就可以让应用无依赖无感知插件
     * 自定义类加载器,负责查找插件和拦截器,为了解决ClassLoader死锁问题,开启类加载器的并行加载模式
     * JDK1.7之前,类加载器在加载类的时候是串行加载的,比如100个类需要加载,那么就排队,加载完上一个在加载下一个,这样加载效率低
     * JDK1.7之后,就提供了类的并行能力,就是把锁的粒度变小,之前ClassLoader加载类的时候,加锁的时候使用自身作为锁的
     * 当委派模型没有严格分层的环境中(出现闭环委托),这个时候就需要类加载器具有并行能力,否则会导致死锁
     */
    registerAsParallelCapable();
}

  • 插件加载 加载指定路径下的插件:
public AgentClassLoader(ClassLoader parent) throws AgentPackageNotFoundException {
    super(parent);
    // 获取探针的目录
    File agentDictionary = AgentPackagePath.getPath();
    classpath = new LinkedList&lt;&gt;();
    // 默认agent目录下的plugin和activations目录作为AgentClassLoader的classpath,只有这两个目录下的jar包才会被AgentClassLoader所加载
    Config.Plugin.MOUNT.forEach(mountFolder -&gt; classpath.add(new File(agentDictionary, mountFolder)));
}
解析插件#org.apache.skywalking.apm.agent.core.plugin.PluginBootstrap#loadPlugins()
PluginResourcesResolver resolver = new PluginResourcesResolver();
// 拿到所有skywalking-plugin.def资源
List&lt;URL&gt; resources = resolver.getResources();

//利用用PluginCfg类将def中的定义转换位PluginDefine对象
for (URL pluginUrl : resources) {
    try {
        // 方法会把skywalking-plugin.def转换为PluginDefine对象(属性:插件名和插件定义类名 )
        PluginCfg.INSTANCE.load(pluginUrl.openStream());
    } catch (Throwable t) {
        LOGGER.error(t, &amp;#34;plugin file [{}] init failure.&amp;#34;, pluginUrl);
    }
}

// 然后遍历所有 `PluginDefine` 对象,通过反射 `defineClass` 字段将每一个插件实例化为\`AbstractClassEnhancePluginDefine` 对象(为什么转换为这个对象,参考官方插件开发指南),添加到plugins列表中,
for (PluginDefine pluginDefine : pluginClassList) {
    try {
        LOGGER.debug(&amp;#34;loading plugin class {}.&amp;#34;, pluginDefine.getDefineClass());
        AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader
            .getDefault()).newInstance();
        plugins.add(plugin);
    } catch (Throwable t) {
        LOGGER.error(t, &amp;#34;load plugin [{}] failure.&amp;#34;, pluginDefine.getDefineClass());
    }
}
//最后将SPI发现并实例化其他插件也添加到plugins列表中返回
plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault()));
插件定义#org.apache.skywalking.apm.agent.core.plugin.PluginFinder#PluginFinder()
public PluginFinder(List&lt;AbstractClassEnhancePluginDefine&gt; plugins) {
    for (AbstractClassEnhancePluginDefine plugin : plugins) {
        // enClass()方法,根据返回的ClassMatch类型进行分类,然后写入到不同的集合中,并在后面的ByteBuddy匹配需要增强类时传入匹配规则
        ClassMatch match = plugin.enhanceClass();

        if (match == null) {
            continue;
        }
        // 适合用NameMatch的插件列表,使用完整类名进行匹配
        if (match instanceof NameMatch) {
            NameMatch nameMatch = (NameMatch) match;
            LinkedList&lt;AbstractClassEnhancePluginDefine&gt; pluginDefines = nameMatchDefine.get(nameMatch.getClassName());
            if (pluginDefines == null) {
                pluginDefines = new LinkedList&lt;AbstractClassEnhancePluginDefine&gt;();
                nameMatchDefine.put(nameMatch.getClassName(), pluginDefines);
            }
            pluginDefines.add(plugin);
        } else {
            // 使用InderectMatch的插件列表,基于类注解、父类、接口、方法注解进行的匹配
            signatureMatchDefine.add(plugin);
        }
        // jdk内置,引导类插件列表
        if (plugin.isBootstrapInstrumentation()) {
            bootstrapClassMatchDefine.add(plugin);
        }
    }
}
4、字节码增强


  • 创建AgentBuilder,AgentBulder是专门用来支持java agent的一个API,在premian()方法中的实现如下:
// 使用ByteBuddy 创建AgentBuilder,使用ByteBuddy来进行字节码增强
final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
// 指定bytebuddy忽略的类
AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore(
        nameStartsWith(&amp;#34;net.bytebuddy.&amp;#34;)
                .or(nameStartsWith(&amp;#34;org.slf4j.&amp;#34;))
                .or(nameStartsWith(&amp;#34;org.groovy.&amp;#34;))
                .or(nameContains(&amp;#34;javassist&amp;#34;))
                .or(nameContains(&amp;#34;.asm.&amp;#34;))
                .or(nameContains(&amp;#34;.reflectasm.&amp;#34;))
                .or(nameStartsWith(&amp;#34;sun.reflect&amp;#34;))
                .or(allSkyWalkingAgentExcludeToolkit())
                .or(ElementMatchers.isSynthetic())); // java编译器编译的时候生成的一些类,这些类找不到源代码
      
// 指定ByteBuddy要拦截的类
agentBuilder.type(pluginFinder.buildMatch())
            .transform(new Transformer(pluginFinder))// 插庄,字节码增强,改字节码,具体的增强逻辑
            // 重定义与retransform的区别就是是否保留之前存在的内容
            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
            .with(new RedefinitionListener())
            // 回调接口,用来在Debug模式下保存增强后的字节码文件
            .with(new Listener())
            .installOn(instrumentation);
增强类匹配pluginFinder.buildMatch()
// 只有符合下述条件的类,才能被ByteBudy字节码增强
    public ElementMatcher&lt;? super TypeDescription&gt; buildMatch() {
        // 建立匹配规则,在内部类中添加第一个默认规则
        ElementMatcher.Junction judge = new AbstractJunction&lt;NamedElement&gt;() {
            @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);
    }

    public List&lt;AbstractClassEnhancePluginDefine&gt; getBootstrapClassMatchDefine() {
        return bootstrapClassMatchDefine;
    }
}
创建增强逻辑,主要在AgentBuilder中的transfor()方法中,其中的new Transformer()就是触发增强逻辑的主要入口
private static class Transformer implements AgentBuilder.Transformer {
    private PluginFinder pluginFinder;

    Transformer(PluginFinder pluginFinder) {
        this.pluginFinder = pluginFinder;
    }
    // 打桩
    @Override
    public DynamicType.Builder&lt;?&gt; transform(final DynamicType.Builder&lt;?&gt; builder, // 当前拦截到类的字节码
                                            final TypeDescription typeDescription, // 简单当成Class, 它包含了类的描述信息
                                            final ClassLoader classLoader, // 加载当前拦截到类的类加载器
                                            final JavaModule module) {
        // 注册URL类加载器,它用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。也就是说,通过URLClassLoader就可以加载指定jar中的class到内存中。
        LoadedLibraryCollector.registerURLClassLoader(classLoader);
        List&lt;AbstractClassEnhancePluginDefine&gt; pluginDefines = pluginFinder.find(typeDescription);
        if (pluginDefines.size() &gt; 0) {
            DynamicType.Builder&lt;?&gt; newBuilder = builder;
            // EnhanceContext是为了保证流程的一个记录器,比如执行了某个步骤之后就会记录下来,防止重复操作
            EnhanceContext context = new EnhanceContext();
            for (AbstractClassEnhancePluginDefine define : pluginDefines) {
                // 调用插件定义的define方法,真正的执行字节码逻辑
                DynamicType.Builder&lt;?&gt; possibleNewBuilder = define.define(
                        typeDescription, newBuilder, classLoader, context);
                if (possibleNewBuilder != null) {
                    newBuilder = possibleNewBuilder;
                }
            }
            if (context.isEnhanced()) {
                LOGGER.debug(&amp;#34;Finish the prepare stage for {}.&amp;#34;, typeDescription.getName());
            }

            return newBuilder; // 被可用插件修改完最终字节码
        }

        LOGGER.debug(&amp;#34;Matched class {}, but ignore by finding mechanism.&amp;#34;, typeDescription.getTypeName());
        return builder;
    }
}
5、BootService启动



根据官方插件开发指南得知,所有插件服务都要实现接口org.apache.skywalking.apm.agent.core.boot.BootService
/**
* The &lt;code&gt;BootService&lt;/code&gt; is an interface to all remote, which need to boot when plugin mechanism begins to work.
* {@link #boot()} will be called when &lt;code&gt;BootService&lt;/code&gt; start up.
* 定义了一个服务的生命周期
*/
public interface BootService {
    void prepare() throws Throwable;

    void boot() throws Throwable;

    void onComplete() throws Throwable;

    void shutdown() throws Throwable;

    /**
     * {@code BootService}s with higher priorities will be started earlier, and shut down later than those {@code BootService}s with lower priorities.
     *
     * 越大越先启动
     * @return the priority of this {@code BootService}.
     */
    default int priority() {
        return 0;
    }
}
除此之外,每个服务实现还需要使用两个注解
@DefaultImplementor 用于标识BootService接口 @OverrideImplmentoe用于覆盖默认BootService实现,通过其value关键字指定要覆盖的默认实现,覆盖实现只能覆盖默认实现
采用Spi加载方式并启动BootService服务, ServiceManager.INSTANCE.boot();
// BootService实例容器,使用map结构方便服务间直接通过Class对象进行调用
    private Map&lt;Class, BootService&gt; bootedServices = Collections.emptyMap();

    public void boot() {
        // 加载所有BootService服务的实现,并写入实例容器
        bootedServices = loadAllServices();
        // 循环调用所有服务的prepare方法
        prepare();
        // 循环调用所有服务的boot方法
        startup();
        // 循环调用所有服务的onComplete方法
        onComplete();
    }
    // 倒叙关闭
具体每一种服务的作用,这里不再赘述
6、注册、启动JVM钩子

// 添加JVM钩子,在JVM退出时关闭所有的BootService服务
Runtime.getRuntime()
        .addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, &amp;#34;skywalking service shutdown thread&amp;#34;));
回复

使用道具 举报

0

主题

2

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2025-3-13 19:40:45 | 显示全部楼层
very good
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver| 手机版| 小黑屋| 零维护

GMT+8, 2025-4-8 14:52 , Processed in 0.107112 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2020, LianLian.

快速回复 返回顶部 返回列表