Principio de realización del agente SkyWalking

Arquitectura de microkernel


SkyWalking Agent utiliza la Arquitectura Microkernel ¿Qué es la Arquitectura Microkernel? La arquitectura de microkernel también se conoce como arquitectura de complemento, que es una arquitectura escalable que se divide funcionalmente. La arquitectura de microkernel se usa generalmente en aplicaciones basadas en productos. Por ejemplo, herramientas de desarrollo IDE como IDEA y Eclipse, el kernel está muy optimizado y se agrega soporte para nuevas funciones como Maven y Gradle en forma de complementos.

Como se muestra en la figura siguiente, la arquitectura del microkernel se divide en dos partes: el sistema central y el módulo enchufable.

En la arquitectura de micro-kernel que se muestra en la figura anterior, la función del kernel es relativamente estable, solo es responsable de administrar el ciclo de vida del complemento y no se modificará continuamente debido a la expansión de las funciones del sistema. Las extensiones funcionales están todas encapsuladas en complementos. Los módulos de complemento son módulos independientes que contienen funciones específicas y pueden ampliar las funciones del sistema central. En general, los diferentes módulos de complementos son independientes entre sí. Por supuesto, puede diseñar un complemento para que dependa de otro complemento, pero debe intentar minimizar la interdependencia entre complementos para evitar dependencias complicadas que causan escalabilidad problemas.

Finalmente, todos los complementos tendrán acceso y administración unificados por parte del sistema del kernel:

1. El sistema kernel debe saber qué complementos cargar. Generalmente, los complementos que se cargarán se determinan mediante archivos de configuración o escaneando ClassPath (como la tecnología SPI presentada anteriormente);
2. El sistema kernel también necesita saber cómo usar estos complementos, microkernel La arquitectura necesita definir un conjunto de especificaciones de complementos, y el sistema del núcleo inicializará e iniciará estos complementos de manera unificada;
3. Aunque los complementos están completamente desacoplados, siempre hay algunos requisitos inesperados en el desarrollo real que harán que los complementos se generen dependencia o se reutilicen algunos complementos subyacentes. En este momento, el kernel debe proporcionar un conjunto de reglas para identificar los mensajes de complementos y correctamente reenvíe mensajes entre complementos y conviértase en una estación de transferencia de mensajes complementarios.

Esto muestra los beneficios de la arquitectura de microkernel:

Se reducen los costos de prueba. Desde la perspectiva de la ingeniería de software, la arquitectura de microkernel divide la parte cambiada de la parte inmutable, lo que reduce el costo de las pruebas y se ajusta al principio abierto y cerrado en el modo de diseño.
estabilidad. Dado que cada módulo de complemento es relativamente independiente, incluso si uno de los complementos tiene un problema, se puede garantizar la estabilidad del sistema kernel y otros complementos.
Escalabilidad. Al agregar nuevas funciones o acceder a nuevos servicios, solo necesita agregar el módulo plug-in correspondiente; cuando la función histórica está fuera de línea, solo necesita eliminar el módulo plug-in correspondiente.
SkyWalking Agent es un método de aterrizaje de arquitectura de microkernel. En la clase anterior, presenté las funciones de cada módulo en SkyWalking. El módulo apm-agent-core corresponde al sistema kernel en la arquitectura del micro-kernel, y los submódulos en el módulo apm-sdk-plugin son todos en la arquitectura del micro-kernel Módulo plug-in.

Descripción general del proceso de inicio de SkyWalking Agent.
Anteriormente, al final de la creación del entorno fuente de SkyWalking, intentamos depurar el código fuente de SkyWalking Agent. La entrada era el método premain () de la clase SkyWalkingAgent en el módulo apm-agent, que completó el proceso de inicio del agente:

  1. Inicialice la información de configuración. En este paso, se cargará el archivo de configuración agent.config, que detectará si los parámetros del Agente Java y las variables de entorno cubren los elementos de configuración correspondientes.
  2. Busque y analice el archivo del complemento skywalking-plugin.def.
  3. AgentClassLoader carga el complemento.
  4. PluginFinder clasifica y administra complementos.
  5. Utilice la biblioteca Byte Buddy para crear AgentBuilder. Aquí, la clase de destino se mejorará dinámicamente de acuerdo con el complemento cargado y se insertará la lógica del punto incrustado.
  6. Utilice JDK SPI para cargar e iniciar el servicio BootService. La implementación de la interfaz BootService se presentará en detalle en una lección posterior.
  7. Agregue un gancho JVM para cerrar todos los servicios BootService cuando la JVM salga.

La implementación específica del método SkywalkingAgent.premain () es la siguiente, con el bloque de código try / catch y la lógica de manejo de excepciones omitidos:

public static void premain(String agentArgs, 

       Instrumentation instrumentation) throws PluginException {

    // 步骤1、初始化配置信息

    SnifferConfigInitializer.initialize(agentArgs); 

    // 步骤2~4、查找并解析skywalking-plugin.def插件文件;

    // AgentClassLoader加载插件类并进行实例化;PluginFinder提供插件匹配的功能

    final PluginFinder pluginFinder = new PluginFinder(

       new PluginBootstrap().loadPlugins());

    // 步骤5、使用 Byte Buddy 库创建 AgentBuilder

    final ByteBuddy byteBuddy = new ByteBuddy()

       .with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));

    new AgentBuilder.Default(byteBuddy)...installOn(instrumentation);

    // 这里省略创建 AgentBuilder的具体代码,后面展开详细说

    // 步骤6、使用 JDK SPI加载的方式并启动 BootService 服务。

    ServiceManager.INSTANCE.boot();

    // 步骤7、添加一个JVM钩子

    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

      public void run() { ServiceManager.INSTANCE.shutdown(); }

    }, "skywalking service shutdown thread"));

}

Después de comprender los pasos principales del inicio de SkyWalking Agent, el resto de esta lección llevará a cabo un análisis en profundidad de cada paso.

Configuracion inicial


Al iniciar dos aplicaciones de demostración, demo-webapp y demo-provider, debe especificar el archivo de configuración agent.confg (parámetro skywalking_config) en las opciones de VM. Los elementos de configuración en el archivo de configuración agent.config son los siguientes:

# 当前应用的服务名称,通过Skywalking Agent上报的Metrics、Trace数据都会

# 携带该信息进行标识

agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}

En el método SnifferConfigInitializer.initialize (), la información de configuración final se completará en los campos estáticos de Config. El proceso de llenado es el siguiente:

Complete toda la información de configuración en el archivo agent.config en los campos estáticos correspondientes en Config.
Analice el valor de las variables de entorno del sistema y sobrescriba los campos estáticos correspondientes en Config.
Analice los parámetros del Agente Java y sobrescriba los campos estáticos correspondientes en Config.
La implementación específica del método SnifferConfigInitializer.initialize () es la siguiente:

public static void initialize(String agentOptions) {

    // 步骤1、加载 agent.config配置文件

    InputStreamReader configFileStream = loadConfig();

    Properties properties = new Properties();

    properties.load(configFileStream);

    for (String key : properties.stringPropertyNames()) {

        String value = (String)properties.get(key);

        // 按照${配置项名称:默认值}的格式解析各个配置项

        properties.put(key, PropertyPlaceholderHelper.INSTANCE

            .replacePlaceholders(value, properties));

    }

    // 填充 Config中的静态字段

    ConfigInitializer.initialize(properties, Config.class);

    // 步骤2、解析环境变量,并覆盖 Config中相应的静态字段

    overrideConfigBySystemProp();

    // 步骤3、解析 Java Agent参数,并覆盖 Config中相应的静态字段

    overrideConfigByAgentOptions(agentOptions);

    // 检测SERVICE_NAME和BACKEND_SERVICE两个配置项,若为空则抛异常(略)

    IS_INIT_COMPLETED = true; // 更新初始化标记

}

El método loadConfig () del paso 1 se cargará primero según la ruta del archivo agent.config especificada por la variable de entorno (skywalking_config). Si la variable de entorno no especifica la configuración de configuración skywalking_, busque el archivo de configuración agent.confg en el directorio config al mismo nivel que skywalking-agent.jar.

Después de cargar la información de configuración en el archivo agent.config en el objeto Propiedades, se utilizará PropertyPlaceholderHelper para analizar la información de configuración, y el valor de configuración actual en el formato "$ {nombre del elemento de configuración: valor predeterminado}" se reemplazará por el valor predeterminado. El resultado del análisis del proveedor de demostración se muestra en la siguiente figura:

Una vez completado el análisis, la información de configuración se completará en los campos estáticos en Config a través de la clase de herramienta ConfigInitializer. Las reglas de llenado específicas son las siguientes:

En el siguiente método overrideConfigBySystemProp (), se atravesarán las variables de entorno (es decir, la colección System.getProperties ()). Si el cambio de entorno comienza con "skywalking", se considera que es la configuración de SkyWalking, que también será completado en la clase Config. Para anular el valor predeterminado en agent.config.

El último método overrideConfigByAgentOptions () analiza los parámetros del Agente Java. Las reglas para llenar la clase Config son las mismas que las de los dos pasos anteriores y no se repetirán.

Hasta este punto, todas las configuraciones requeridas para el inicio de SkyWalking Agent se han completado en Config, y puede acceder directamente a los campos estáticos correspondientes en Config cuando use la información de configuración más adelante.

Principio de carga enchufable


Después de completar la inicialización de la clase Config, SkyWalking Agent comienza a escanear y cargar el paquete jar del complemento de SkyWalking Agent en el directorio especificado.

AgentClassLoader


SkyWalking Agent usa un cargador de clases personalizado —— AgentClassLoader cuando se cargan complementos. La razón para personalizar el cargador de clases es no introducir paquetes jar de complementos de SkyWalking en la ruta de clase de la aplicación, de modo que la aplicación pueda depender y desconocer los complementos .

Optimización de carga paralela


El bloque de código estático de AgentClassLoader llamará al método tryRegisterAsParallelCapable (), que intentará habilitar la función de carga paralela del JDK a través de la reflexión:

private static void tryRegisterAsParallelCapable() {

    Method[] methods = ClassLoader.class.getDeclaredMethods();

    for (int i = 0; i < methods.length; i++) {

        Method method = methods[i];

        String methodName = method.getName();

        // 查找 ClassLoader中的registerAsParallelCapable()静态方法

        if ("registerAsParallelCapable".equalsIgnoreCase(methodName)) 

        {

            method.setAccessible(true);

            method.invoke(null); // 调用registerAsParallelCapable()方法

            return;

        }

    }

}


Al usar ClassLoader para cargar una clase, la JVM realizará la sincronización de bloqueo, por lo que podemos usar el mecanismo de carga de clases para implementar singletons. En Java 6, el método ClassLoader.loadClass () está sincronizado con el bloqueo sincronizado, lo que requiere una competencia global por un bloqueo, que es ligeramente ineficaz.

Se proporcionan dos modos de bloqueo después de Java 7:

En el modo serial, el objeto bloqueado es el ClassLoader mismo, que tiene el mismo comportamiento que en Java 6. El
otro es el modo de carga paralelo que se activa después de llamar al método registerAsParallelCapable (). Al cargar una clase en modo paralelo, el bloqueo se adquirirá de acuerdo con el nombre de la clase. El fragmento de implementación correspondiente en el método ClassLoader.loadClass () es el siguiente:

protected Class<?> loadClass(String name, boolean resolve)

    throws ClassNotFoundException{

    // getClassLoadingLock() 方法会返回加锁的对象

    synchronized (getClassLoadingLock(name)) { 

       ... ... // 加载指定类,具体加载细节不展开介绍

    }

}

protected Object getClassLoadingLock(String className) {

    Object lock = this;

    if (parallelLockMap != null) { // 检测是否开启了并行加载功能

        Object newLock = new Object();

        // 若开启了并行加载,则一个className对应一把锁;否则还是只

        // 对当前ClassLoader进行加锁

        lock = parallelLockMap.putIfAbsent(className, newLock);

        if (lock == null) {

            lock = newLock;

        }

    }

    return lock;

}


Implementación principal de AgentClassLoader
El campo classpath se inicializa en el método de construcción de AgentClassLoader, que apunta al directorio que AgentClassLoader escaneará (el directorio de complementos y el directorio de activaciones al mismo nivel que el paquete skywalking-agent.jar), como se muestra a continuación:

private List<File> classpath; 

public AgentClassLoader(ClassLoader parent) {

    super(parent); // 双亲委派机制

    // 获取 skywalking-agent.jar所在的目录

    File agentDictionary = AgentPackagePath.getPath();

    classpath = new LinkedList<File>();

    // 初始化 classpath集合,指向了skywalking-agent.jar包同目录的两个目录

    classpath.add(new File(agentDictionary, "plugins"));

    classpath.add(new File(agentDictionary, "activations"));

}


Como cargador de clases, el trabajo principal de AgentClassLoader es cargar clases (o archivos de recursos) desde su Classpath, correspondientes a su método findClass () y al método findResource (). Aquí hay un breve vistazo a la implementación del método findClass ():

// 在下面的getAllJars()方法中会扫描全部jar文件,并缓存到

// allJars字段(List<Jar>类型)中,后续再次扫描时会重用该缓

private List<Jar> allJars;

protected Class<?> findClass(String name) {

    List<Jar> allJars = getAllJars();  // 扫描过程比较简单,不再展开介绍

    String path = name.replace('.', '/').concat(".class");

    for (Jar jar : allJars) { // 扫描所有jar包,查找类文件

        JarEntry entry = jar.jarFile.getJarEntry(path);

        if (entry != null) {

            URL classFileUrl = new URL("jar:file:" + 

                jar.sourceFile.getAbsolutePath() + "!/" + path);

            byte[] data = ...;// 省略读取".class"文件的逻辑                          // 加载类文件内容,创建相应的Class对象

            return defineClass(name, data, 0, data.length);

        }

    } // 类查找失败,直接抛出异常

    throw new ClassNotFoundException("Can't find " + name);

}


El método findResource () recorrerá todos los paquetes jar almacenados en caché en la colección allJars, buscará el archivo de recursos especificado y lo devolverá. La lógica transversal es similar a la del método findClass () y no se realizarán más análisis.

Finalmente, hay un campo estático DEFAULT_LOADER en AgentClassLoader, que registra el AgentClassLoader predeterminado, como se muestra a continuación, pero tenga en cuenta que AgentClassLoader no es un singleton. Verá otros lugares donde se crea AgentClassLoader más adelante.

private static AgentClassLoader DEFAULT_LOADER;


Analizar la definición del complemento


Cada complemento de agente definirá un archivo skywalking-plugin.def, como se muestra en la siguiente figura tomcat-7.x-8.x-plugin:

El contenido del archivo skywalking-plugin.def en el complemento tomcat-7.x-8.x-plugin es el siguiente, cada línea de la cual es la definición de una clase de complemento:

tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define 

.TomcatInstrumentation

tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define

.ApplicationDispatcherInstrumentation


PluginResourcesResolver es el solucionador de recursos del complemento del Agente. Leerá el archivo skywalking-plugin.def en todos los complementos del Agente a través del método findResource () en AgentClassLoader.

Después de que AbstractClassEnhancePluginDefine
obtenga el archivo skywalking-plugin.def de todos los complementos, PluginCfg lo analizará línea por línea y lo convertirá en un objeto PluginDefine. Hay dos campos en PluginDefine:

// 插件名称,以 tomcat-7.x-8.x-plugin 插件第一行为例,就是tomcat-7.x/8.x

private String name; 

// 插件类,对应上例中的 org.apache.skywalking.apm.plugin.tomcat78x.define

// .TomcatInstrumentation

private String defineClass;

PluginCfg es una clase de herramienta singleton implementada mediante enumeración. La lógica es muy simple y no se introducirá.

A continuación, atravesará todos los objetos PluginDefine y creará una instancia de la clase de complemento registrada en el campo defineClass mediante la reflexión. La lógica principal es la siguiente:

for (PluginDefine pluginDefine : pluginClassList) {

    // 注意,这里使用类加载器是默认的AgentClassLoader实例

    AbstractClassEnhancePluginDefine plugin =

        (AbstractClassEnhancePluginDefine)

            Class.forName(pluginDefine.getDefineClass(), true,

            AgentClassLoader.getDefault()).newInstance();

    plugins.add(plugin); // 记录AbstractClassEnhancePluginDefine 对象

}


La clase abstracta AbstractClassEnhancePluginDefine es la clase principal de nivel superior de todas las clases de complementos del Agente. Define cuatro métodos principales, que determinan qué clases de destino debe mejorar una clase de complemento, cómo mejorar y qué lógica insertar, como se muestra. abajo:

  • MétodohanceClass (): el ClassMatch devuelto se utiliza para que coincida con la clase de destino que se mejorará con el complemento actual.
  • método define (): la entrada de la lógica de mejora de la clase de complemento, la capa inferior llamará al siguiente métodohancement () y al método testigoClass ().
  • El métodohance (): donde se ejecuta realmente la lógica de mejora.
  • Método WitnessClass (): un componente de código abierto puede tener varias versiones, y el complemento utilizará este método para identificar diferentes versiones del componente para evitar que se mejoren versiones incompatibles.

La función específica y la implementación de cada método se presentarán en detalle en la clase siguiente Debe saber que hay aproximadamente cuatro métodos en AbstractClassEnhancePluginDefine.

El método
ClassMatchhanceClass () determina la clase de destino que se mejorará con una clase de complemento, y el valor de retorno es un objeto de tipo ClassMatch. ClassMatch es similar a un filtro, que se puede hacer coincidir con la clase de destino de muchas formas. La implementación de la interfaz ClassMatch es la siguiente:

** NameMatch: ** Coincide con el nombre de la clase de destino de acuerdo con su campo className (tipo de cadena).
** IndirectMatch: ** Se definen dos métodos en la subinterfaz.

// Junction是Byte Buddy中的类,可以通过and、or等操作串联多个ElementMatcher

// 进行匹配

ElementMatcher.Junction buildJunction(); 

// 用于检测传入的类型是否匹配该Match

boolean isMatch(TypeDescription typeDescription);


MultiClassNameMatch: especificará una colección matchClassNames, y las clases de esta colección son las clases objetivo.
ClassAnnotationMatch: Haga coincidir la clase de destino de acuerdo con las anotaciones marcadas en la clase.
MethodAnnotationMatch: Haga coincidir la clase de destino de acuerdo con las anotaciones marcadas en el método.
HierarchyMatch: haga coincidir la clase de destino de acuerdo con la clase o interfaz principal.
Aquí tomamos ClassAnnotationMatch como un ejemplo para analizar El campo de anotaciones (tipo String []) especifica las anotaciones que se verificarán para este objeto ClassAnnotationMatch. En el método buildJunction (), se creará un cruce correspondiente para cada anotación y se conectará en un formulario y se devolverá, como se muestra a continuación:

public ElementMatcher.Junction buildJunction() {

    ElementMatcher.Junction junction = null;

    for (String annotation : annotations) { // 遍历全部注解

        if (junction == null) { 

            // 该Junction用于检测类是否标注了指定注解

            junction = buildEachAnnotation(annotation);

        } else {// 使用 and 方式将所有Junction对象连接起来

            junction = junction.and(buildEachAnnotation(annotation));

        }

    }

    junction = junction.and(not(isInterface())); // 排除接口

    return junction;

}


La implementación del método isMatch () es similar. Solo la clase que contiene todas las anotaciones especificadas se puede hacer coincidir correctamente, como se muestra a continuación:

public boolean isMatch(TypeDescription typeDescription) {

    List<String> annotationList = 

        new ArrayList<String>(Arrays.asList(annotations));

    // 获取该类上的注解

    AnnotationList declaredAnnotations = 

          typeDescription.getDeclaredAnnotations();

    // 匹配一个删除一个

    for (AnnotationDescription annotation : declaredAnnotations) {

        annotationList.remove(annotation

              .getAnnotationType().getActualName());

    }

    if (annotationList.isEmpty()) { // 删空了,就匹配成功了

        return true;

    }

    return false;

}


Los principios de implementación de otras interfaces ClassMatch son similares, por lo que no lo analizaremos más, si está interesado, puede mirar el código.

PluginFinder
PluginFinder es un buscador de AbstractClassEnhancePluginDefine, que puede encontrar la colección AbstractClassEnhancePluginDefine para mejorarla de acuerdo con una clase determinada.

En el constructor de PluginFinder, se recorrerá el AbstractClassEnhancePluginDefine que se ha instanciado en el curso anterior y se clasificará de acuerdo con el tipo ClassMatcher devuelto por el métodohanceClass (), y se obtendrán los dos conjuntos siguientes:

// 如果返回值为NameMatch类型,则相应 AbstractClassEnhancePluginDefine 

// 对象会记录到该集合

private Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine;

// 如果是其他类型返回值,则相应 AbstractClassEnhancePluginDefine 

// 对象会记录到该集合

private List<AbstractClassEnhancePluginDefine> signatureMatchDefine;


El método find () es un método de consulta expuesto por PluginFinder, que atraviesa la colección nameMatchDefine y la colección signatureMatchDefine sucesivamente, y determina todos los complementos coincidentes mediante el método ClassMatch.isMatch (). La implementación del método find () no es complicada, así que no lo volveré a presentar.

AgentBuilder
ha analizado los pasos para cargar la información de configuración, inicializar la clase Config, buscar en el archivo skywalking-pluing.def e inicializar el objeto AbstractClassEnhancePluginDefine durante el inicio de Skywalking Agent. Ahora vamos a presentar cómo Byte Buddy usa la clase de complemento cargada para mejorar el método de destino.

En el paso 5 del método SkywalkingAgent.premain (), primero se crea el objeto ByteBuddy. Como se mencionó en la lección básica de Byte Buddy, es uno de los objetos básicos de Byte Buddy:

// 步骤5、通过Byte Buddy API创建Agent

final ByteBuddy byteBuddy = new ByteBuddy()

   .with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));


Los elementos de configuración correspondientes de Config.Agent.IS_OPEN_DEBUGGING_CLASS en el archivo agent.config son:

agent.is_open_debugging_class


Si está configurado como verdadero, las clases generadas dinámicamente se enviarán al directorio de depuración.

A continuación, cree un objeto AgentBuilder, que es una API de la biblioteca Byte Buddy específicamente para admitir el Agente Java, como se muestra a continuación:

new AgentBuilder.Default(byteBuddy) // 设置使用的ByteBuddy对象

.ignore(nameStartsWith("net.bytebuddy.")// 不会拦截下列包中的类

       .or(nameStartsWith("org.slf4j."))

       .or(nameStartsWith("org.apache.logging."))

       .or(nameStartsWith("org.groovy."))

       .or(nameContains("javassist"))

       .or(nameContains(".asm."))

       .or(nameStartsWith("sun.reflect"))

       .or(allSkyWalkingAgentExcludeToolkit()) // 处理 Skywalking 的类

       // synthetic类和方法是由编译器生成的,这种类也需要忽略

       .or(ElementMatchers.<TypeDescription>isSynthetic()))

.type(pluginFinder.buildMatch())// 拦截

.transform(new Transformer(pluginFinder)) // 设置Transform

.with(new Listener()) // 设置Listener

.installOn(instrumentation)

Explique brevemente el método de AgentBuilder utilizado aquí:

  • Método Ignore (): ignora las clases en el paquete especificado y no se realizará ninguna mejora de interceptación en estas clases.
  • Método type (): interceptación de acuerdo con el ElementMatcher entrante cuando se carga la clase, la clase objetivo interceptada será mejorada por el Transformer especificado en el método transform ().
  • Método transform (): El transformador especificado aquí mejorará la clase previamente interceptada.
  • with () método: agregue un Listener para escuchar los eventos activados por AgentBuilder.

En primer lugar, el objeto ElementMatcher devuelto por el método PluginFInder.buildMatch () conectará las reglas coincidentes de todos los complementos (es decir, el ClassMatch devuelto por el métodohanceClass () del complemento) por OR. En este De esta forma, se cruzarán todas las clases que puedan coincidir con todos los complementos.

Veamos el oyente agregado en el método with (): SkywalkingAgent.Listener, que hereda la interfaz AgentBuilder.Listener. Cuando se monitorea un evento de transformación, decidirá si persistir la clase mejorada en un archivo de clase y guardarlo en el especificado de acuerdo con la configuración IS_OPEN_DEBUGGING_CLASS En el directorio de registro. Tenga en cuenta que esta operación debe bloquearse, lo que afectará el rendimiento del sistema. Generalmente, solo se activa en un entorno de prueba, pero no en un entorno de producción.

Por último, mire Skywalking.Transformer, que implementa la interfaz AgentBuilder.Transformer, y su método transform () es la entrada a la clase de destino de mejora del complemento. Skywalking.Transformer utilizará PluginFinder para encontrar el complemento que coincida con la clase de destino (es decir, el objeto AbstractClassEnhancePluginDefine) y luego se lo entregará a AbstractClassEnhancePluginDefine para completar la mejora. La implementación principal es la siguiente:

public DynamicType.Builder<?> transform(DynamicType.Builder<?>builder,

    TypeDescription typeDescription, // 被拦截的目标类

    ClassLoader classLoader,  // 加载目标类的ClassLoader

    JavaModule module) {

    // 从PluginFinder中查找匹配该目标类的插件,PluginFinder的查找逻辑不再重复

    List<AbstractClassEnhancePluginDefine> pluginDefines =

           pluginFinder.find(typeDescription);

    if (pluginDefines.size() >0){ 

        DynamicType.Builder<?>newBuilder = builder;

        EnhanceContext context = new EnhanceContext();

        for (AbstractClassEnhancePluginDefinedefine : pluginDefines) {

            // AbstractClassEnhancePluginDefine.define()方法是插件入口,

            // 在其中完成了对目标类的增强

            DynamicType.Builder<?>possibleNewBuilder = 

                 define.define(typeDescription, 

                      newBuilder, classLoader,context);

            if (possibleNewBuilder != null) {

                // 注意这里,如果匹配了多个插件,会被增强多次

                newBuilder = possibleNewBuilder;

            }

        }

        return newBuilder;

    }

    return builder;

}


Tenga en cuenta aquí: si una clase coincide con varios complementos, se mejorará varias veces. Cuando abra el elemento de configuración IS_OPEN_DEBUGGING_CLASS, verá varios archivos de clase correspondientes.

Cargar BootService
El último paso del inicio de SkyWalking Agent es utilizar la tecnología JDK SPI presentada anteriormente para cargar todas las clases de implementación de la interfaz BootService. La interfaz BootService define el comportamiento del servicio principal de SkyWalking Agent, que se define de la siguiente manera:

public interface BootService {

    void prepare() throws Throwable;

    void boot() throws Throwable;

    void onComplete() throws Throwable;

    void shutdown() throws Throwable;

}


ServiceManager es el administrador de la instancia de BootService, principalmente responsable de administrar el ciclo de vida de la instancia de BootService.

ServiceManager es un singleton. La capa inferior mantiene una colección bootedServices (tipo Map <Class, BootService>) y registra la instancia correspondiente de cada implementación de BootService. El método boot () es el método principal de ServiceManager. Primero crea una instancia de todas las implementaciones de la interfaz BootService a través del método load (), como se muestra a continuación:

void load(List<BootService> allServices) { 

    // 很明显使用了 JDK SPI 技术加载并实例化 META-INF/services下的全部 

    // BootService接口实现

    Iterator<BootService> iterator = ServiceLoader.load(

        BootService.class,AgentClassLoader.getDefault()).iterator();

    while (iterator.hasNext()) {

        // 记录到方法参数传入的 allServices集合中

        allServices.add(iterator.next()); 

    }

}


En el archivo resource / META-INF.services / org.apache.skywalking.apm.agent.core.boot.BootService del módulo apm-agent-core, se registran las clases de implementación de la interfaz BootService que el ServiceManager debe cargar, como se muestra a continuación. La clase presentará sus funciones específicas en detalle en la siguiente clase:

org.apache.skywalking.apm.agent.core.remote.TraceSegmentServiceClient

org.apache.skywalking.apm.agent.core.context.ContextManager

org.apache.skywalking.apm.agent.core.sampling.SamplingService

org.apache.skywalking.apm.agent.core.remote.GRPCChannelManager

org.apache.skywalking.apm.agent.core.jvm.JVMService

org.apache.skywalking.apm.agent.core.remote.ServiceAndEndpointRegisterClient

org.apache.skywalking.apm.agent.core.context.ContextManagerExtendService


Después de cargar los tipos de implementación de BootService anteriores, ServiceManager procesará las anotaciones @DefaultImplementor y @OverrideImplementor en BootService:

  • La anotación @DefaultImplementor se utiliza para identificar la implementación predeterminada de la interfaz BootService.
  • La anotación @OverrideImplementor se utiliza para anular la implementación predeterminada de BootService. El campo de valor especifica la implementación predeterminada que se anulará.

La lógica de superposición de BootService se muestra en la siguiente figura:

Después de determinar la implementación de BootService que se utilizará, ServiceManager inicializará uniformemente la implementación de BootService en la colección bootServices. También en el método ServiceManager.boot (), los métodos prepare (), startup () y onComplete () implementados por BootService se llamarán uno por uno. La implementación específica es la siguiente:

public void boot() {

    bootedServices = loadAllServices(); 

    prepare(); // 调用全部BootService对象的prepare()方法

    startup(); // 调用全部BootService对象的boot()方法

    onComplete(); // 调用全部BootService对象的onComplete()方法

}


Al final del proceso de inicio del Agente Skywalking, se agregará un enlace de salida de JVM y todos los servicios BootService iniciados en la sección anterior se cerrarán mediante el método ServiceManager.shutdown ().

Los fragmentos de código relevantes en el método SkywalkingAgent.premain () son los siguientes:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

    @Override public void run() {

        ServiceManager.INSTANCE.shutdown(); 

    }

}, "skywalking service shutdown thread"));

 

Supongo que te gusta

Origin blog.csdn.net/lewee0215/article/details/109636349
Recomendado
Clasificación