当前位置:首页 > 单片机 > 架构师社区
[导读]作者:未完成交响曲,资深Java工程师!目前在某一线互联网公司任职,架构师社区合伙人! 本文源码基于Pinpoint 2.0.3-SNAPSHOT版本 官方开源地址:https://github.com/naver/pinpoint Pinpoint Agent Pinpoint通过字节码增强技术来实现无侵入式的调用链采集。

分布式系统性能监控工具,初探Pinpoint Agent启动源码

作者:未完成交响曲,资深Java工程师!目前在某一线互联网公司任职,架构师社区合伙人!


本文源码基于Pinpoint 2.0.3-SNAPSHOT版本
官方开源地址:https://github.com/naver/pinpoint

Pinpoint Agent

Pinpoint通过字节码增强技术来实现无侵入式的调用链采集。其核心实现是基于JVM的Java Agent机制。

我们使用Pinpoint时,需要在Java应用启动参数上加上-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar参数,这样,当我们的Java应用启动时,会同时启动Agent。

Pinpoint Agent在启动的时候,会加载plugin文件夹下所有的插件,这些插件会对特定class类修改字节码,在一些指定的方法调用前后加上链路采集逻辑(比如Dubbo中AbstractProxyInvokerinvoke()方法),这样就实现了调用链监控功能。

Pinpoint官方文档中的原理描述:

分布式系统性能监控工具,初探Pinpoint Agent启动源码
在这里插入图片描述

在pintpoin-bootstrap模块中,我们可以在pom文件中看到maven插件里有MANIFEST相关的配置,指定了 Premain-Class,这个配置在打包时也会生成到MANIFEST.MF文件中:
分布式系统性能监控工具,初探Pinpoint Agent启动源码
在这里插入图片描述

执行premain()方法

通过上述配置可知,当我们启动接入了pinpoint的Java应用时,会先执行PinpointBootStrap.premain()方法:

去掉了日志等非核心逻辑代码

public static void premain(String agentArgs, Instrumentation instrumentation) {
    // 1.设置启动状态,避免重复初始化
    final boolean success = STATE.start();
    if (!success) {
        return;
    }

    // 2.初始化和解析启动参数
    final JavaAgentPathResolver javaAgentPathResolver = JavaAgentPathResolver.newJavaAgentPathResolver();
    final String agentPath = javaAgentPathResolver.resolveJavaAgentPath();
    final Map<String, String> agentArgsMap = argsToMap(agentArgs);
    final ClassPathResolver classPathResolver = new AgentDirBaseClassPathResolver(agentPath);

    // 3.查找核心jar包
    final AgentDirectory agentDirectory = resolveAgentDir(classPathResolver);
    BootDir bootDir = agentDirectory.getBootDir();
    appendToBootstrapClassLoader(instrumentation, bootDir);

    // 4.获取类加载器,加载核心jar中的类
    ClassLoader parentClassLoader = getParentClassLoader();
    final ModuleBootLoader moduleBootLoader = loadModuleBootLoader(instrumentation, parentClassLoader);
    PinpointStarter bootStrap = new PinpointStarter(parentClassLoader, agentArgsMap, agentDirectory, instrumentation, moduleBootLoader);

    // 5.启动bootStrap
    if (!bootStrap.start()) {
        logPinpointAgentLoadFail();
    }
}

可以看到premain()方法有两个参数,最重要的是这个instrumentation对象

Instrumentation是Java提供的一个来自JVM的接口,该接口提供了一系列查看和操作Java类定义的方法,例如修改类的字节码、向classLoader的classpath下加入jar文件等,

PinpointBootStrap.premain()方法中,主要完成了相关jar包的查找和加载,然后将一系列配置以及instrumentation对象构造成PinpointStarter对象,并执行start()方法完成后续的启动:

boolean start() {
    // 1.读取agentId和applicationName
    final AgentIds agentIds = resolveAgentIds();
    final String agentId = agentIds.getAgentId();
    final String applicationName = agentIds.getApplicationName();
    final boolean isContainer = new ContainerResolver().isContainer();

    try {
        // 2.解析并加载配置
        final Properties properties = loadProperties();
        ProfilerConfig profilerConfig = new DefaultProfilerConfig(properties);

        // 3.设置日志路径和版本信息到systemProperty
        saveLogFilePath(agentDirectory);
        savePinpointVersion();

        // 4.创建AgentClassLoader
        URL[] urls = resolveLib(agentDirectory);
        final ClassLoader agentClassLoader = createClassLoader("pinpoint.agent", urls, parentClassLoader);
        if (moduleBootLoader != null) {
            moduleBootLoader.defineAgentModule(agentClassLoader, urls);
        }
        final String bootClass = getBootClass();
        AgentBootLoader agentBootLoader = new AgentBootLoader(bootClass, agentClassLoader);

        final List<String> pluginJars = agentDirectory.getPlugins();

        // 5.构建AgentOption,并作为参数通过反射机制构建Agent(DefaultAgent)
        AgentOption option = createAgentOption(agentId, applicationName, isContainer, profilerConfig, instrumentation, pluginJars, agentDirectory);
        Agent pinpointAgent = agentBootLoader.boot(option);

        // 6.启动死锁监控线程、agent数据上报线程、注册ShutdownHook
        pinpointAgent.start();
        pinpointAgent.registerStopHandler();

    } catch (Exception e) {
        return false;
    }
    return true;
}

初始化上下文

上面过程其实还是加载配置并构建一些对象,这里面最核心的逻辑是构建Agent对象,执行了DefaultAgent类的构造器,初始化了上下文:

new DefaultAgent()

┆┈ DefaultAgent.newApplicationContext()

┆┈┈┈ new DefaultApplicationContext()

这里我们直接看DefaultApplicationContext类的构造器中的关键逻辑:

public DefaultApplicationContext(AgentOption agentOption, ModuleFactory moduleFactory) {
    // 1.获取Instrumentation对象
    final Instrumentation instrumentation = agentOption.getInstrumentation();

    // 2.构建Guice ioc容器,用于依赖注入
    final Module applicationContextModule = moduleFactory.newModule(agentOption);
    this.injector = Guice.createInjector(Stage.PRODUCTION, applicationContextModule);

    // 3.通过Guice注入一系列对象
    this.profilerConfig = injector.getInstance(ProfilerConfig.class);
    this.interceptorRegistryBinder = injector.getInstance(InterceptorRegistryBinder.class);
    this.instrumentEngine = injector.getInstance(InstrumentEngine.class);
    this.classFileTransformer = injector.getInstance(ClassFileTransformer.class);
    this.dynamicTransformTrigger = injector.getInstance(DynamicTransformTrigger.class);

    // 4.通过instrumentation对象注册类转换器
    instrumentation.addTransformer(classFileTransformer, true);

    ...
}

绑定TransformCallback

Guice是谷歌开源的一个轻量级的依赖注入框架,pinpoint依靠Guice管理各种对象。

在初始化ioc容器的过程中,会遍历plugin目录下的所有插件对其进行初始化,调用过程如下:

ApplicationServerTypeProvider.get()

|— PluginContextLoadResultProvider.get()

|—— new DefaultPluginContextLoadResult()

|——— DefaultProfilerPluginContextLoader.load()

|———— DefaultProfilerPluginContextLoader.setupPlugin()

|————— DefaultPluginSetup.setupPlugin()

|—————— XxxPlugin.setup()(具体Plugin实现)

DubboPlugin为例,在setup()方法中主要对dubbo中的核心类进行转换器绑定:

@Override
public void setup(ProfilerPluginSetupContext context) {
    DubboConfiguration config = new DubboConfiguration(context.getConfig());
    ...
    this.addTransformers();
}

private void addTransformers() {
    // 为dubbo核心rpc调用类绑定Transform关系
    transformTemplate.transform("com.alibaba.dubbo.rpc.protocol.AbstractInvoker", AbstractInvokerTransform.class);
    transformTemplate.transform("com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker", AbstractProxyInvokerTransform.class);
}

再来看看其中一个Transform类都做了些什么:

public static class AbstractInvokerTransform implements TransformCallback {
   @Override
    public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
        // 指定目标类(上一步绑定的类)
        final InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
        // 指定目标方法(方法名、参数)
        InstrumentMethod invokeMethod = target.getDeclaredMethod("invoke""com.alibaba.dubbo.rpc.Invocation");
        if (invokeMethod != null) {
            // 为此方法添加拦截器
            invokeMethod.addInterceptor(DubboConsumerInterceptor.class);
        }
        return target.toBytecode();
    }
}

可以看到,这个类实现了TransformCallback接口,这个接口从名字上可以看出是一个回调接口,而在其doInTransform()方法中,是通过字节码增强的方式,为com.alibaba.dubbo.rpc.protocol.AbstractInvoker类的invoke()方法添加了一个拦截器DubboConsumerInterceptor

DubboConsumerInterceptor实现了AroundInterceptor接口的before()after()方法,这里可以看出和Spring AOP很相似了,而在拦截器中,主要是对dubbo的RPC调用进行trace、span等链路追踪信息的记录。

动态类加载

在上下文初始化时,Pinpointinstrumentation注册了一个Transformer,该接口只定义个一个方法transform(),该方法会在加载新class类或者重新加载class类时调用,其调用路径如下:

DefaultClassFileTransformerDispatcher.transform()

|— BaseClassFileTransformer.transform()

|—— MatchableClassFileTransformerDelegate.transform()

|——— TransformCallback.doInTransform()

可以看到,最后执行的就是我们在上面执行XxxPlugin.setup()方法时配置的回调接口,即对指定的方法进行字节码增强。而Java应用启动后,加载的就是我们增强后的类,从而实现链路监控或其他的功能。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

分布式系统性能监控工具,初探Pinpoint Agent启动源码

长按订阅更多精彩▼

分布式系统性能监控工具,初探Pinpoint Agent启动源码

如有收获,点个在看,诚挚感谢

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭