第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > 分布式系统性能监控工具 初探Pinpoint Agent启动源码

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

时间:2021-03-21 20:47:15

相关推荐

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

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

本文源码基于Pinpoint 2.0.3-SNAPSHOT版本

官方开源地址:/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官方文档中的原理描述:

在这里插入图片描述

在pintpoin-bootstrap模块中,我们可以在pom文件中看到maven插件里有MANIFEST相关的配置,指定了Premain-Class,这个配置在打包时也会生成到MANIFEST.MF文件中:

在这里插入图片描述

执行premain()方法

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

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

publicstaticvoidpremain(StringagentArgs,Instrumentationinstrumentation){//1.设置启动状态,避免重复初始化finalbooleansuccess=STATE.start();if(!success){return;}//2.初始化和解析启动参数finalJavaAgentPathResolverjavaAgentPathResolver=JavaAgentPathResolver.newJavaAgentPathResolver();finalStringagentPath=javaAgentPathResolver.resolveJavaAgentPath();finalMap<String,String>agentArgsMap=argsToMap(agentArgs);finalClassPathResolverclassPathResolver=newAgentDirBaseClassPathResolver(agentPath);//3.查找核心jar包finalAgentDirectoryagentDirectory=resolveAgentDir(classPathResolver);BootDirbootDir=agentDirectory.getBootDir();appendToBootstrapClassLoader(instrumentation,bootDir);//4.获取类加载器,加载核心jar中的类ClassLoaderparentClassLoader=getParentClassLoader();finalModuleBootLoadermoduleBootLoader=loadModuleBootLoader(instrumentation,parentClassLoader);PinpointStarterbootStrap=newPinpointStarter(parentClassLoader,agentArgsMap,agentDirectory,instrumentation,moduleBootLoader);//5.启动bootStrapif(!bootStrap.start()){logPinpointAgentLoadFail();}}

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

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

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

booleanstart(){//1.读取agentId和applicationNamefinalAgentIdsagentIds=resolveAgentIds();finalStringagentId=agentIds.getAgentId();finalStringapplicationName=agentIds.getApplicationName();finalbooleanisContainer=newContainerResolver().isContainer();try{//2.解析并加载配置finalPropertiesproperties=loadProperties();ProfilerConfigprofilerConfig=newDefaultProfilerConfig(properties);//3.设置日志路径和版本信息到systemPropertysaveLogFilePath(agentDirectory);savePinpointVersion();//4.创建AgentClassLoaderURL[]urls=resolveLib(agentDirectory);finalClassLoaderagentClassLoader=createClassLoader("pinpoint.agent",urls,parentClassLoader);if(moduleBootLoader!=null){moduleBootLoader.defineAgentModule(agentClassLoader,urls);}finalStringbootClass=getBootClass();AgentBootLoaderagentBootLoader=newAgentBootLoader(bootClass,agentClassLoader);finalList<String>pluginJars=agentDirectory.getPlugins();//5.构建AgentOption,并作为参数通过反射机制构建Agent(DefaultAgent)AgentOptionoption=createAgentOption(agentId,applicationName,isContainer,profilerConfig,instrumentation,pluginJars,agentDirectory);AgentpinpointAgent=agentBootLoader.boot(option);//6.启动死锁监控线程、agent数据上报线程、注册ShutdownHookpinpointAgent.start();pinpointAgent.registerStopHandler();}catch(Exceptione){returnfalse;}returntrue;}

初始化上下文

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

new DefaultAgent()

┆┈DefaultAgent.newApplicationContext()

┆┈┈┈new DefaultApplicationContext()

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

publicDefaultApplicationContext(AgentOptionagentOption,ModuleFactorymoduleFactory){//1.获取Instrumentation对象finalInstrumentationinstrumentation=agentOption.getInstrumentation();//2.构建Guiceioc容器,用于依赖注入finalModuleapplicationContextModule=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中的核心类进行转换器绑定:

@Overridepublicvoidsetup(ProfilerPluginSetupContextcontext){DubboConfigurationconfig=newDubboConfiguration(context.getConfig());...this.addTransformers();}privatevoidaddTransformers(){//为dubbo核心rpc调用类绑定Transform关系transformTemplate.transform("com.alibaba.dubbo.rpc.protocol.AbstractInvoker",AbstractInvokerTransform.class);transformTemplate.transform("com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker",AbstractProxyInvokerTransform.class);}

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

publicstaticclassAbstractInvokerTransformimplementsTransformCallback{@Overridepublicbyte[]doInTransform(Instrumentorinstrumentor,ClassLoaderloader,StringclassName,Class<?>classBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer)throwsInstrumentException{//指定目标类(上一步绑定的类)finalInstrumentClasstarget=instrumentor.getInstrumentClass(loader,className,classfileBuffer);//指定目标方法(方法名、参数)InstrumentMethodinvokeMethod=target.getDeclaredMethod("invoke","com.alibaba.dubbo.rpc.Invocation");if(invokeMethod!=null){//为此方法添加拦截器invokeMethod.addInterceptor(DubboConsumerInterceptor.class);}returntarget.toBytecode();}}

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

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

动态类加载

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

DefaultClassFileTransformerDispatcher.transform()

|—BaseClassFileTransformer.transform()

|——MatchableClassFileTransformerDelegate.transform()

|———TransformCallback.doInTransform()

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

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。