• 一文探索【skywalking】如何通过agent实现启动流程


    概述

    本文的主角是一款非常著名且使用的链路追踪工具:SkyWalking

    SkyWalking是一个APM(application performance monitor)系统,专门为微服务、云原生和基于容器(Docker、Kubernetes、Mesos)的架构而设计,包含了云原生架构下的分布式系统的监控、跟踪、诊断功能。

    通过agent的方式,可以做到对代码无侵入式的介入,实时实现整个服务链路的监控。本文的将要介绍的重点是SkyWalking如何通过agent实现如此全面的监控功能。这里提到的agent是在java中使用的agent技术,本文所讲的内容也是依托于java为基础的。

    一、什么是agent?

    1.1 静态agent

    1.1.1 简介

    JDK从1.5开始,引入了agent机制,用户可以通过-javaagent参数使用agent技术。agent技术可以使JVM在加载class文件之前先加载agent文件,通过修改JVM传入的字节码来实现自定义代码的注入。

    为什么称之为静态agent?因为使用此种方式,不需要通过指定VM参数的方式,所以想要修改agent必须要对服务进行重启。

    1.1.2 使用

    下面我们动手实现一个简单的静态agent。

    1.1.2.1 配置

    实现静态agent需要配置agent的启动类,用来发现方法premain,这是实现静态agent的启动方法,因为是在jvm加载类之前,所以叫做pre-agent

    静态agent通常有两种方式:

    • MANIFEST.MF 文件

      需要在resources下创建META-INF文件夹,在内部创建MANIFEST.MF文件,其格式如下(注意最后一行要换行,否则idea或报错):

      1. Manifest-Version: 1.0
      2. Premain-Class: com.wjbgn.warriors.agent.StaticAgentTest
      3. Can-Redefine-Classes: true
      4. Can-Retransform-Classes: true

      除此之外,还需要引入maven-assembly-plugin插件,否则MANIFEST.MF文件的内容会被maven打包后的内容覆盖掉。

      1. <plugin>
      2. <groupId>org.apache.maven.plugins</groupId>
      3. <artifactId>maven-assembly-plugin</artifactId>
      4. <configuration>
      5. <descriptorRefs>
      6. <descriptorRef>jar-with-dependencies</descriptorRef>
      7. </descriptorRefs>
      8. <archive>
      9. <manifestFile>
      10. src/main/resources/META-INF/MANIFEST.MF
      11. </manifestFile>
      12. </archive>
      13. </configuration>
      14. </plugin>
    • 【推荐】引入编译插件 maven-assembly-plugin

      直接使用maven-assembly-plugin插件就能达到使用agent的效果,所以推荐此方式。

      1. <plugin>
      2. <groupId>org.apache.maven.plugins</groupId>
      3. <artifactId>maven-assembly-plugin</artifactId>
      4. <configuration>
      5. <descriptorRefs>
      6. <descriptorRef>jar-with-dependencies</descriptorRef>
      7. </descriptorRefs>
      8. <archive>
      9. <manifestEntries>
      10. <Premain-Class>com.wjbgn.warriors.agent.StaticAgentTest</Premain-Class>
      11. <Can-Redefine-Classes>true</Can-Redefine-Classes>
      12. <Can-Retransform-Classes>true</Can-Retransform-Classes>
      13. </manifestEntries>
      14. </archive>
      15. </configuration>
      16. </plugin>

    1.1.2.2 测试

    • 创建一个测试类:

      1. package com.wjbgn.warriors.agent;
      2. import java.lang.instrument.Instrumentation;
      3. /**
      4. * @description: 静态agent测试类
      5. * @author:weirx
      6. * @date:2022/6/30 15:13
      7. * @version:3.0
      8. */
      9. public class StaticAgentTest {
      10. /**
      11. * description: 静态agent启动类
      12. * @param agentArgs
      13. * @param inst
      14. * @return: void
      15. * @author: weirx
      16. * @time: 2022/6/30 15:14
      17. */
      18. public static void premain(String agentArgs, Instrumentation inst) {
      19. // 在springboot启动前打印一下文字
      20. System.out.println("this is static agent");
      21. // 打印vm参数配置的agent参数
      22. System.out.println(agentArgs);
      23. }
      24. }
    • 使用assembly插件打包,在idea中:

    1. 当然也可以使用命令:`mvn assembly:single`
    2. 打包后文件在项目的`target`下。
    • 在idea添加启动参数:

    1. 蓝色部分是携带的参数,其余部分指定agent的jar包位置,完整命令如下:
    2. ```
    3. -javaagent:E:\workspace\warriors\warriors-agent\target\warriors-agent-0.0.1-SNAPSHOT-jar-with-dependencies.jar=[testAgnet]
    4. ```
    • 启动项目

    如上所示,成功输出我们预期内容。
    

    1.2 动态agent

    1.2.1 简介

    JDK在1.6版本开始,又引入了attach方式,对于运行中的应用程序,可以对其附加agent,这一操作让我们可以动态的修改已经加载的类。这也是称之为动态agent的原因。

    通过VirtualMachineattach(pid)可以获得VirtualMachine实例,之后通过loadAgent(agent path)方法将指定的agent加载到正在运行的JVM当中,实现动态加载。

    1.2.2 使用

    1.2.2.1 配置

    想要使用VirtualMachine,需要引入对应的依赖:

    1. <dependency>
    2. <groupId>com.sun</groupId>
    3. <artifactId>tools</artifactId>
    4. <version>1.8</version>
    5. <scope>system</scope>
    6. <systemPath>${java.home}/../lib/tools.jar</systemPath>
    7. </dependency>

    修改配置文件:

    1. <plugin>
    2. <groupId>org.apache.maven.plugins</groupId>
    3. <artifactId>maven-assembly-plugin</artifactId>
    4. <configuration>
    5. <descriptorRefs>
    6. <descriptorRef>jar-with-dependencies</descriptorRef>
    7. </descriptorRefs>
    8. <archive>
    9. <manifestEntries>
    10. <Agent-Class>com.wjbgn.warriors.agent.DynamicAgentTest</Agent-Class>
    11. <Can-Redefine-Classes>true</Can-Redefine-Classes>
    12. <Can-Retransform-Classes>true</Can-Retransform-Classes>
    13. </manifestEntries>
    14. </archive>
    15. </configuration>
    16. </plugin>

    1.2.2.2 准备测试环境

    我们静态agent工程启动,保持在运行中:

    查看其进程id:

    1. E:\workspace\warriors>jps
    2. 15248 Launcher
    3. 6480 Jps
    4. 13764 RemoteMavenServer36
    5. 4532 WarriorsAgentApplication
    6. 9188

    WarriorsAgentApplication的进程id是4532

    1.2.2.3 测试

    • 创建测试类

      1. package com.wjbgn.warriors.agent;
      2. import com.sun.tools.attach.AgentInitializationException;
      3. import com.sun.tools.attach.AgentLoadException;
      4. import com.sun.tools.attach.AttachNotSupportedException;
      5. import com.sun.tools.attach.VirtualMachine;
      6. import java.io.IOException;
      7. import java.lang.instrument.Instrumentation;
      8. /**
      9. * @description: 动态agent测试
      10. * @author:weirx
      11. * @date:2022/6/30 15:53
      12. * @version:3.0
      13. */
      14. public class DynamicAgentTest {
      15. /**
      16. * description: 动态agent,通过attach和loadAgent进行探针载入
      17. *
      18. * @param agentArgs
      19. * @param inst
      20. * @return: void
      21. * @author: weirx
      22. * @time: 2022/6/30 15:54
      23. */
      24. public static void agentmain(String agentArgs, Instrumentation inst) {
      25. // 打印以下日志
      26. System.out.println("this is static agent");
      27. // 打印参数
      28. System.out.println(agentArgs);
      29. }
      30. public static void main(String[] args) {
      31. VirtualMachine virtualMachine = null;
      32. try {
      33. // 指定进程号
      34. virtualMachine = VirtualMachine.attach("4532");
      35. // 指定agent jar包路径和参数
      36. virtualMachine.loadAgent("E:\workspace\warriors\warriors-agent\target\warriors-agent-0.0.1-SNAPSHOT-jar-with-dependencies.jar", "agentTest");
      37. } catch (AttachNotSupportedException e) {
      38. e.printStackTrace();
      39. } catch (IOException e) {
      40. e.printStackTrace();
      41. } catch (AgentLoadException e) {
      42. e.printStackTrace();
      43. } catch (AgentInitializationException e) {
      44. e.printStackTrace();
      45. }
      46. }
      47. }

      如上所示,包含两部分:

      • agentmain 启动程序
      • main方法执行attach和loadAgent方法,加载agent到当前指定进程id的应用程序中。
    • 使用assembly插件打包,在idea中:

    1. 当然也可以使用命令:`mvn assembly:single`
    2. 打包后文件在项目的`target`下。
    • 启动程序

    运行上面的main方法,查看正在运行的4532项目的控制台:

    如上所示,已经输出了动态注入的内容。

    1.3 Instrumentation

    无论是静态agent,还是动态,我们发现在其对应的方法当中,都有一个Instrumentation的入参,那么它是做什么的呢?

    使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。

    1.4 小结

    关于静态agent动态agent的简单介绍以及使用方式就介绍这么多,当然还有很多细节和有用的方面没有涉及,但是本节的重点意义在于让大家了解agent的工作方式,方便我们后面学习skywalking的agent实现。源码阅读过程中会针对涉及到的agent技术进行讲解。

    二、SkyWalking源码分析

    前面花费大量的篇章来解释什么是agent,从本节开始,正式进入源码阅读阶段。

    需要单独说明的

    • 如果你是用的是8.7.0之前的版本,skywalking的服务端和agent的代码是放在同一个项目工程skywalking下的。

    • 如果使用8.7.0之后的版本,agent的相关代码被抽离出skywalkin当中,不同的语言会对应不同的agent,需要根据需要去自行选择,比如java使用skywalking-java

    • 在开始学习源码前,建议按照上面的说明提供的地址拷贝一份源码到本地,有利于对源码的学习。

    skywalking是采用静态agent的方式,所以我们首先要找到它的agent启动类SkyWalkingAgent.java的位置:

    2.1 premain 方法

    我们从上至下分析下premain的逻辑,只看关键代码位置:

    2.1.1 加载插件

    2.1.1.1 定义插件寻找器

    1. // 定义一个插件寻找器
    2. final PluginFinder pluginFinder;

    2.1.1.2 加载配置

    1. // 初始化指定参数,我们启动项目时候,可以指定服务的名称等参数,内部还加载配置文件agent.config的内容
    2. SnifferConfigInitializer.initializeCoreConfig(agentArgs);

    使用过skywalking的都知道,我们有两种配置被监控你服务的方式:

    • vm参数指定
    • agent.conf 配置

    不论何种方式,都会通过此行代码进行加载。

    2.1.1.3 加载插件

    1. //初始化插件寻找器
    2. //使用PluginResourcesResolver加载并定义所有插件
    3. pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());

    new PluginBootstrap().loadPlugins()方法内部使用PluginResourcesResolver加载所有的组件,并且要求组件必须定义在文件skywalking-plugin.def当中:

    如上图所示,我们常用的组件都会有其对应的插件定义在这个工程中,需要加载的类都会通过skywalking-plugin.def进行配置,该文件格式如下所示:

    image.png

    new PluginBootstrap().loadPlugins()加载插件的流程如下:

    上图流程简介如下:

    • 初始化PluginBootstrap调用其loadPlugins()
    • loadPlugins方法内部初始化PluginResourcesResolver,并调用其getResources()
    • getResource方法内部使用AgentClassLoadergetResources("skywalking-plugin.def")方法获取所有插件下的skywalking-plugin.def文件。
    • AgentClassLoader的底层实际是ClassLoader,使用它的根据文件名称获取文件方法getResources(String name),得到Enumeration<URL>集合,通过遍历获取List<URL>
    • 遍历List<URL>,通过PluginCfg.INSTANCE.load加载插件。
      • 通过pluginDefine = reader.readLine()逐行读取内容
      • 通过PluginDefine.build(pluginDefine)构建读取到的内容
      • 实际build内部就是通过=进行截取,分别获取插件名称pluginName和类定义defineClass
      • 通过构造返回插件定义的实例new PluginDefine(pluginName, defineClass)
    • 返回插件定义集合List<PluginDefine>
    • 遍历插件集合,使用反射的方式创建插件实例。
    • 返回所有已经加载的插件集合List<AbstractClassEnhancePluginDefine>

    得到所有的插件集合后,new PluginFinder(List<AbstractClassEnhancePluginDefine>)的主要作用是为已加载的插件做缓存,并且提供快速查找已加载插件的能力。

    2.1.2 创建AgentBuilder

    ByteBuddy:Byte Buddy是一个字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。

    2.1.2.1 实例化ByteBuddy

    1. final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));

    其中IS_OPEN_DEBUGGING_CLASS如果开启,skywalking会相对于agent根目录的位置创建文件夹,记录这些被插桩的类。用来和skywalking开发者解决兼容性问题。

    2.1.2.2 创建AgentBulider

    用来定义ByteBuddy的一些行为。如下忽略无关的类:

    1. AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore(
    2. nameStartsWith("net.bytebuddy.")
    3. .or(nameStartsWith("org.slf4j."))
    4. .or(nameStartsWith("org.groovy."))
    5. .or(nameContains("javassist"))
    6. .or(nameContains(".asm."))
    7. .or(nameContains(".reflectasm."))
    8. .or(nameStartsWith("sun.reflect"))
    9. .or(allSkyWalkingAgentExcludeToolkit())
    10. .or(ElementMatchers.isSynthetic()));

    2.1.2.3 定义边缘类集合:EdgeClasses

    1. JDK9ModuleExporter.EdgeClasses edgeClasses = new JDK9ModuleExporter.EdgeClasses();

    其实EdgeClasses就是一个List,其中会包含ByteBuddy的核心类。

    1. public class ByteBuddyCoreClasses {
    2. private static final String SHADE_PACKAGE = "org.apache.skywalking.apm.dependencies.";
    3. public static final String[] CLASSES = {
    4. SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.RuntimeType",
    5. SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.This",
    6. SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.AllArguments",
    7. SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.AllArguments$Assignment",
    8. SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.SuperCall",
    9. SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.Origin",
    10. SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.Morph",
    11. };
    12. }

    通过这些报名,我们发现其实它们是一些annotation(注解)

    2.1.2.4 将EdgeClasses注入BoostrapClassLoader

    1. BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses);

    这个位置涉及到后面要讲解的东西,暂时先不讲解。

    但是关于类加载器的内容要简单描述一下,我们了解类加载器的同学应该知道java的类加载关系:

    为什么要注入到BoostrapClassLoader?

    自定义的ClassLoader只能在最下层,而AgentClassLoader通过字节码修改的类,是不能够被BootStrapClassLoader直接使用的,所以需要注入进去。

    2.1.2.5 打开读边界

    这行主要为了解决jdk9中模块系统的跨模块类访问问题。与本文重点无关,略过。

    1. JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses);

    2.1.2.6 AgentBuilder属性设置

    1. agentBuilder
    2. // 指定ByteBuddy修改的符合条件的类
    3. .type(pluginFinder.buildMatch())
    4. // 指定字节码增强工具
    5. .transform(new Transformer(pluginFinder))
    6. //指定字节码增强模式,REDEFINITION覆盖修改内容,RETRANSFORMATION保留修改内容(修改名称),
    7. .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
    8. // 注册监听器-监听异常情况,输出日子
    9. .with(new RedefinitionListener())
    10. // 对transform和异常情况监听,输出日志
    11. .with(new Listener())
    12. // 将定义好的agent注册到instrumentation
    13. .installOn(instrumentation);

    2.1.3 加载服务

    1. ServiceManager.INSTANCE.boot();

    如上所示ServiceManager基于ServerLoader实现,是JDK提供的一种SPI机制。

    boot()方法:

    1. public void boot() {
    2. // 加载所有的服务到bootServices
    3. bootedServices = loadAllServices();
    4. // 准备
    5. prepare();
    6. // 启动
    7. startup();
    8. //完成
    9. onComplete();
    10. }

    2.1.3.1 bootedServices是什么?

    1. private Map<Class, BootService> bootedServices = Collections.emptyMap();

    其中的BootService是一个接口,其包含了服务的生命周期,插件开始工作时,需要被启动,如下所示:

    1. public interface BootService {
    2. /**
    3. * 准备
    4. * @throws Throwable
    5. */
    6. void prepare() throws Throwable;
    7. /**
    8. * 启动
    9. * @throws Throwable
    10. */
    11. void boot() throws Throwable;
    12. /**
    13. * 完成
    14. * @throws Throwable
    15. */
    16. void onComplete() throws Throwable;
    17. /**
    18. * 停止
    19. * @throws Throwable
    20. */
    21. void shutdown() throws Throwable;
    22. /**
    23. * 优先级,优先级高的BootService会优先启动
    24. */
    25. default int priority() {
    26. return 0;
    27. }
    28. }

    所有实现了BootService接口的服务都会在此被加载。

    2.1.3.2 loadAllServices()

    这个方法通过jdk提供的SPI机制,ServiceLoader将需要的类加载进来,而这些类被定义在指定的配置文件当中:

    配置文件内容如下:

    1. org.apache.skywalking.apm.agent.core.remote.TraceSegmentServiceClient
    2. org.apache.skywalking.apm.agent.core.context.ContextManager
    3. org.apache.skywalking.apm.agent.core.sampling.SamplingService
    4. org.apache.skywalking.apm.agent.core.remote.GRPCChannelManager
    5. org.apache.skywalking.apm.agent.core.jvm.JVMMetricsSender
    6. org.apache.skywalking.apm.agent.core.jvm.JVMService
    7. org.apache.skywalking.apm.agent.core.remote.ServiceManagementClient
    8. org.apache.skywalking.apm.agent.core.context.ContextManagerExtendService
    9. org.apache.skywalking.apm.agent.core.commands.CommandService
    10. org.apache.skywalking.apm.agent.core.commands.CommandExecutorService
    11. org.apache.skywalking.apm.agent.core.profile.ProfileTaskChannelService
    12. org.apache.skywalking.apm.agent.core.profile.ProfileSnapshotSender
    13. org.apache.skywalking.apm.agent.core.profile.ProfileTaskExecutionService
    14. org.apache.skywalking.apm.agent.core.meter.MeterService
    15. org.apache.skywalking.apm.agent.core.meter.MeterSender
    16. org.apache.skywalking.apm.agent.core.context.status.StatusCheckService
    17. org.apache.skywalking.apm.agent.core.remote.LogReportServiceClient
    18. org.apache.skywalking.apm.agent.core.conf.dynamic.ConfigurationDiscoveryService
    19. org.apache.skywalking.apm.agent.core.remote.EventReportServiceClient
    20. org.apache.skywalking.apm.agent.core.ServiceInstanceGenerator

    通过下面的方法将上面配置的所有实现了bootService的类加载到allServices当中。

    1. void load(List<BootService> allServices) {
    2. for (final BootService bootService : ServiceLoader.load(BootService.class, AgentClassLoader.getDefault())) {
    3. allServices.add(bootService);
    4. }
    5. }

    在上一步加载完全部的类之后,需要去遍历这些类,得到一个bootedServices的集合。在看代码逻辑之前,需要看下skywalking定义的两个注解:

    • @DefaultImplementor 默认实现
    • @OverrideImplementor 覆盖实现

    带有@DefaultImplementor注解的类,表示它会有类去继承它,继承它的类需要带有 @OverrideImplementor注解,并指定继承的类的名称,例如:

    默认实现类:

    1. @DefaultImplementor
    2. public class JVMMetricsSender implements BootService, Runnable, GRPCChannelListener
    1. 继承它的类:
    1. @OverrideImplementor(JVMMetricsSender.class)
    2. public class KafkaJVMMetricsSender extends JVMMetricsSender implements KafkaConnectionStatusListener

    在了解了skywalking的默认类和继承类的机制后,有代码逻辑如下:

    1. private Map<Class, BootService> loadAllServices() {
    2. Map<Class, BootService> bootedServices = new LinkedHashMap<>();
    3. List<BootService> allServices = new LinkedList<>();
    4. // SPI加载
    5. load(allServices);
    6. // 遍历
    7. for (final BootService bootService : allServices) {
    8. Class<? extends BootService> bootServiceClass = bootService.getClass();
    9. // 是否带有默认实现
    10. boolean isDefaultImplementor = bootServiceClass.isAnnotationPresent(DefaultImplementor.class);
    11. if (isDefaultImplementor) {// 是默认实现
    12. // 是默认实现,没有添加到bootedServices
    13. if (!bootedServices.containsKey(bootServiceClass)) {
    14. //加入bootedServices
    15. bootedServices.put(bootServiceClass, bootService);
    16. } else {
    17. //ignore the default service
    18. }
    19. } else {// 不是默认实现
    20. // 是否是覆盖实现
    21. OverrideImplementor overrideImplementor = bootServiceClass.getAnnotation(OverrideImplementor.class);
    22. // 不是覆盖
    23. if (overrideImplementor == null) {
    24. // bootedServices没有
    25. if (!bootedServices.containsKey(bootServiceClass)) {
    26. //加入bootedServices
    27. bootedServices.put(bootServiceClass, bootService);
    28. } else {
    29. throw new ServiceConflictException("Duplicate service define for :" + bootServiceClass);
    30. }
    31. } else {
    32. // 是覆盖,value获取的是其继承的类targetService
    33. Class<? extends BootService> targetService = overrideImplementor.value();
    34. // 如果bootedServices已经包含targetService
    35. if (bootedServices.containsKey(targetService)) {
    36. // 判断targetServices是否是默认实现
    37. boolean presentDefault = bootedServices.get(targetService)
    38. .getClass()
    39. .isAnnotationPresent(DefaultImplementor.class);
    40. // 是默认实现
    41. if (presentDefault) {
    42. // 添加进去
    43. bootedServices.put(targetService, bootService);
    44. } else {
    45. // 没有默认实现,不能覆盖,抛出异常
    46. throw new ServiceConflictException(
    47. "Service " + bootServiceClass + " overrides conflict, " + "exist more than one service want to override :" + targetService);
    48. }
    49. } else {
    50. // 是覆盖实现,它覆盖的默认实现还没有被加载进来
    51. bootedServices.put(targetService, bootService);
    52. }
    53. }
    54. }
    55. }
    56. return bootedServices;
    57. }

    2.1.3.3 prepare(),startup(),onComplete()

    在加载完全部的类之后,还有准备,启动和完成等分操作,它们的代码实现相同,如下所示:

    1. private void prepare() {
    2. // 获取所有的类
    3. bootedServices.values().stream()
    4. // 根据优先级排序,BootService的priority
    5. .sorted(Comparator.comparingInt(BootService::priority))
    6. // 遍历
    7. .forEach(service -> {
    8. try {
    9. // 执行每一个BootService的实现类的prepare()方法
    10. service.prepare();
    11. } catch (Throwable e) {
    12. LOGGER.error(e, "ServiceManager try to pre-start [{}] fail.", service.getClass().getName());
    13. }
    14. });
    15. }
    16. private void startup() {
    17. bootedServices.values().stream()
    18. // 根据优先级排序,BootService的priority
    19. .sorted(Comparator.comparingInt(BootService::priority))
    20. // 遍历
    21. .forEach(service -> {
    22. try {
    23. // 执行每一个BootService的实现类的boot()方法
    24. service.boot();
    25. } catch (Throwable e) {
    26. LOGGER.error(e, "ServiceManager try to start [{}] fail.", service.getClass().getName());
    27. }
    28. });
    29. }
    30. private void onComplete() {
    31. // 遍历
    32. for (BootService service : bootedServices.values()) {
    33. try {
    34. // 执行每一个BootService的实现类的onComplete()方法
    35. service.onComplete();
    36. } catch (Throwable e) {
    37. LOGGER.error(e, "Service [{}] AfterBoot process fails.", service.getClass().getName());
    38. }
    39. }
    40. }

    2.1.4 ShutdownHook

    为skywalking的运行服务添加一个shutdown的钩子。

    1. Runtime.getRuntime()
    2. .addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));

    shutdown方法,与准备和启动方法不同之处在于,shutdown的排序方式是按照优先级的倒序排序,为了优雅的停机,后启动的服务,先停机:

    1. public void shutdown() {
    2. bootedServices.values().stream().
    3. // 排序,按照优先级的倒序
    4. sorted(Comparator.comparingInt(BootService::priority).reversed())
    5. .forEach(service -> {
    6. try {
    7. // 执行每个服务的shutdown
    8. service.shutdown();
    9. } catch (Throwable e) {
    10. LOGGER.error(e, "ServiceManager try to shutdown [{}] fail.", service.getClass().getName());
    11. }
    12. });
    13. }

    2.2 小结

    本章节用了不小的篇幅讲解premain方法的源码启动过程,主要包括以下的方面:

    • 配置加载
    • 插件加载
    • AgentBUilder创建
    • skywalking服务的加载

    本章主要在于讲解启动流程,代码量较大,涉及到字节码增强的关键暂时未讲解。

    三、总结

    到此为止,关于skywalking的相关启动流程就基本介绍完成了。之所以说是基本完成,是因为内部还以些关于插件启动流程的部分没有具体讲解,比如:

    • transform的工作流程
    • 三种插装方式的原理
      • 静态方法插桩
      • 构造器插桩
      • 实例方法插桩

    限于篇幅原因,后续文章继续讲解。

    通过本篇文章,相信您一定也有了不少的收获:

    • 静态agent和动态agent的概念和使用方式
    • skywalking使用def文件定义插件的方式,即插件的加载过程
    • skywaling通过ByteBuddy实现字节码增强
    • 类加载器的相关知识
    • BootService的定义和使用方式
    • JDK SPI方式实现类加载。
    • Skywalking所特有的默认实现,覆盖实现的注解定义方式。

    除了以上具体的,还会看到诸如策略模式、建造者模式、观察者等设计模式。

    阅读源码,虽然很多时候会让你感到晦涩难懂,但是当你坚持下来,收获绝对是意想不到的。养成阅读源码的好习惯,将会在编码工作中起到很大的帮助。

  • 相关阅读:
    [附源码]计算机毕业设计springboot良辰之境影视评鉴系统
    Nginx安装、配置
    猿创征文|Vue源码分析(Render渲染函数)
    CSS中alpha没有效果
    基于ffmpeg下载.ts流媒体分片传输文件自动合并单独完整mp4文件,windows
    mysql常用命令
    C盘爆满上热搜,简单几招释放几十G空间,一下子就不红了
    卫浴服务信息展示预约小程序的作用如何
    Dapr Outbox 执行流程
    hadoop、hive、DBeaver的环境搭建及使用
  • 原文地址:https://blog.csdn.net/chenxuyuana/article/details/125634084