Pinpoint的原理是什么?
还有其他的都是无侵入式的,他的原理是什么?
1 个回答
为了击碎你伪装的表面,我只能用尽全身力气挥出那惊鸿的半月弧光。云说### Pinpoint的原理 Pinpoint 是一款用于监控和跟踪分布式系统的APM(应用性能管理)工具。它通过字节码增强技术(也称动态探针技术)实现无侵入式的调用链采集。其核心实现基于JVM的javaagent机制。 ### Pinpoint原理 Pinpoint 在启动时通过设置如下参数来指定 Pinpoint Agent 的加载路径: ``` -javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar ``` 在启动时,agent 在加载应用类文件之前会拦截并修改字节码,在类方法调用的前后加上链路采集逻辑,从而实现调用链采集功能。 JavaAgent 的底层机制主要依赖于 JVMTI(JVM Tool Interface)。JVMTI 是 JVM 暴露出来的一些供用户扩展的接口集合,基于事件驱动,允许开发者在 JVM 执行到某些逻辑时插入自己的代码。JVMTI 的具体实现通过 Java 的 instrument 包来实现,为 JVM 提供外挂支持。 ### Instrumentation机制 Instrumentation 支持启动时加载和运行时加载两种方式,分别实现 JVMTI 的 `Agent_OnLoad` 和 `Agent_OnAttach` 方法。Pinpoint 采用的是启动时加载方式,通过 `premain` 方法在应用启动前进行字节码增强。 #### 实现步骤 1. **打包 Pinpoint Agent 类**:Pinpoint Agent 类打包成 jar 文件,META-INF/MANIFEST.MF 必须包含 `Premain-Class` 属性: ```java Manifest-Version: 1.0 Premain-Class: com.navercorp.pinpoint.bootstrap.PinpointBootStrap Archiver-Version: Plexus Archiver Built-By: user Can-Redefine-Classes: true Pinpoint-Version: 1.6.0-SNAPSHOT Can-Retransform-Classes: true Created-By: Apache Maven 3.5.2 Build-Jdk: 1.8.0_152 ``` 2. **实现 `premain` 方法**:在启动类中实现 `instrument` 包规定的 `premain` 方法。应用在启动前会优先调用这个方法: ```java public static void premain(String agentArgs, Instrumentation instrumentation) { if (agentArgs == null) { agentArgs = ""; } logger.info(ProductInfo.NAME + " agentArgs:" + agentArgs); final boolean success = STATE.start(); if (!success) { logger.warn("pinpoint-bootstrap already started. skipping agent loading."); return; } Map<String, String> agentArgsMap = argsToMap(agentArgs); final ClassPathResolver classPathResolver = new AgentDirBaseClassPathResolver(); if (!classPathResolver.verify()) { logger.warn("Agent Directory Verify fail. skipping agent loading."); logPinpointAgentLoadFail(); return; } BootstrapJarFile bootstrapJarFile = classPathResolver.getBootstrapJarFile(); appendToBootstrapClassLoader(instrumentation, bootstrapJarFile); PinpointStarter bootStrap = new PinpointStarter(agentArgsMap, bootstrapJarFile, classPathResolver, instrumentation); if (!bootStrap.start()) { logPinpointAgentLoadFail(); } } ``` `agentArgs` 是 `premain` 函数得到的程序参数,随同 `-javaagent` 一起传入。`instrumentation` 是一个 `java.lang.instrument.Instrumentation` 的实例,由 JVM 自动传入,集中几乎所有的功能方法,例如类定义的转换和操作。 3. **调用 `PinpointStarter.start()` 方法**:跟踪 `premain` 代码,主要调用 `PinpointStarter.start()` 方法: ```java boolean start() { try { // 加载配置 ProfilerConfig profilerConfig = DefaultProfilerConfig.load(configPath); // 解析库路径 List<URL> libUrlList = resolveLib(classPathResolver); AgentClassLoader agentClassLoader = new AgentClassLoader(libUrlList.toArray(new URL[libUrlList.size()])); final String bootClass = getBootClass(); agentClassLoader.setBootClass(bootClass); logger.info("pinpoint agent [" + bootClass + "] starting..."); // 创建Agent选项 AgentOption option = createAgentOption(agentId, applicationName, profilerConfig, instrumentation, pluginJars, bootstrapJarFile, serviceTypeRegistryService, annotationKeyRegistryService); Agent pinpointAgent = agentClassLoader.boot(option); pinpointAgent.start(); registerShutdownHook(pinpointAgent); logger.info("pinpoint agent started normally."); } catch (Exception e) { logger.warn(ProductInfo.NAME + " start failed.", e); return false; } return true; } ``` 4. **调用 `pinpointAgent.start()` 方法**:继续跟踪代码,主要调用 `pinpointAgent.start()` 方法,而 `pinpointAgent` 在此处的实现类是 `DefaultAgent`: ```java public DefaultAgent(AgentOption agentOption, final InterceptorRegistryBinder interceptorRegistryBinder) { this.profilerConfig = agentOption.getProfilerConfig(); this.instrumentation = agentOption.getInstrumentation(); this.agentOption = agentOption; // 默认使用ASM字节码引擎 this.classPool = createInstrumentEngine(agentOption, interceptorRegistryBinder); logger.info("DefaultAgent classLoader:{}", this.getClass().getClassLoader()); // 加载插件 pluginContexts = loadPlugins(agentOption); // 将插件转换器列表转成HashMap this.classFileTransformer = new ClassFileTransformerDispatcher(this, pluginContexts); this.dynamicTransformService = new DynamicTransformService(instrumentation, classFileTransformer); // 包装转换器 ClassFileTransformer wrappedTransformer = wrapClassFileTransformer(classFileTransformer); // 调用JVM的Instrumentation工具方法添加转换器 instrumentation.addTransformer(wrappedTransformer, true); } ``` 可以看出,`addTransformer` 方法并没有指明要转换哪个类,此时并未发生实际的字节码转换。转换发生在 `premain` 函数执行之后,`main` 函数执行之前,这时每装载一个类,`transform` 方法就会执行一次,看看是否需要转换,此时 `wrappedTransformer` 最终会通过 `dynamicTransformService` 封装的 `HashMap<className, classFileTransformer>` 来判断当前加载的应用类是否需要转换。 ### Pinpoint字节码转换过程 下图展示了 Pinpoint 的字节码转换过程:  1. JVM初始化并通过System ClassLoader加载Pinpoint Agent类;创建Instrumentation接口实例并调用Pinpoint Agent的Premain方法,并自动传入Instrumentation实例; 2. Pinpoint Agent加载plugins插件,将其中的transformer类注册到Instrumentation实例中; 3. System ClassLoader加载其他的应用Java类,此时将调用注册的transformer方法对要加载的java类进行字节码转换; 4. JVM将转换后的class放入方法区。 ### 总结 Pinpoint 通过在 JVM 启动时使用 javaagent 机制加载字节码增强逻辑,能够在不修改应用程序代码的情况下实现对分布式系统的监控和跟踪。具体实现过程中,Pinpoint 使用 Instrumentation 接口拦截和修改类的字节码,并通过插件机制加载所需的转换逻辑,实现对目标方法的调用链采集。 其主要流程包括: 1. JVM 初始化并加载 Pinpoint Agent 类。 2. Pinpoint Agent 加载插件,并注册字节码转换器。 3. 在应用类加载过程中,通过注册的转换器对类的字节码进行修改。 4. 修改后的类被加载到方法区,实现调用链数据的采集。 这种无侵入式的设计使得 Pinpoint 能够对现有系统进行快速集成和部署,提供了高效的分布式系统性能监控和问题排查能力。编辑于 2023-12-31 18:54 •IP属地 北京举报评论收藏
公众号进入网站体验更佳

