本文共 9773 字,大约阅读时间需要 32 分钟。
01
Pinpoint是什么_Pinpoint是一款全链路分析工具,提供了无侵入式的调用链监控、方法执行详情查看、应用状态信息监控等功能。基于Google Dapper论文进行的实现。核心思想就是在服务各节点彼此调用的时候,记录并传递一个应用级别的标记,这个标记可以用来关联各个服务节点之间的关系。比如两个节点之间使用 HTTP 作为请求协议的话,那么这些标记就会被加入到HTTP头中,各应用的Agent在进行上报的时候,将该标记以及对应的上下级应用上报到Pinpoint collector中,通过该标记标识请求,并将各个应用串联成完整的调用链路。Pinpoint的特点如下
• 分布式事务跟踪,跟踪跨分布式应用的消息• 自动检测应用拓扑,帮助你搞清楚应用的架构• 水平扩展以便支持大规模服务器集群• 提供代码级别的可见性以便轻松定位失败点和瓶颈• 使用字节码增强技术,添加新功能而无需修改代码Pinpoint针对不同的组件提供了丰富的插件,其支持以下模块:• JDK 6+• Tomcat 6/7/8, Jetty 8/9, JBoss EAP 6, Resin 4, Websphere 6/7/8, Vertx 3.3/3.4/3.5• Spring, Spring Boot (Embedded Tomcat, Jetty)• Apache HTTP Client 3.x/4.x, JDK HttpConnector, GoogleHttpClient, OkHttpClient, NingAsyncHttpClient• Thrift Client, Thrift Service, DUBBO PROVIDER, DUBBO CONSUMER• MySQL, Oracle, MSSQL, CUBRID,POSTGRESQL, MARIA• Arcus, Memcached, Redis, CASSANDRA• iBATIS, MyBatis• DBCP, DBCP2, HIKARICP• gson, Jackson, Json Lib• log4j, Logback02
插件知识和相关数据结合_pinpoint插件能够在代码级别对感兴趣的方法进行拦截,可以针对业务代码,第三方包等,如记录方法的执行时间,参数,方法返回结果,在RPC调用中插入标识ID以记录调用关系等, pinpoint插件很容易扩展,官方也提供了很多插件,基本覆盖了常用的组件,如hystrix,dubbo等,部署即可用。1、插件结构Pinpoint 插件由TraceMetadataProvider和ProfilerPlugin的实现组成TraceMetadataProvider实现 给pinpoint agent,collector,web组件提供ServiceType和AnnotationKey,ProfilerPlugin 实现用于agent转换目标类以记录追踪数据。Pinpoint插件以jar文件的形式部署。Agent在plugin目录下用ServiceLoader 搜索TraceMetadataProvider和ProfilerPlugin的实现,web和collector在 WEB-INF/lib目录下搜索,ServiceLoader要求provider配置文件存在于META-INF/services目录下,所以在plugin jar中必须放置以下文件,实现类通过 Java 的服务发现机制进行加载。1META-INF/services/com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin2META-INF/services/com.navercorp.pinpoint.common.trace.TraceMetadataProvider
TraceMetadataProvider 实现类中提供ServiceTypes和AnnotationKeys。
2.1.1 ServiceTypes
每个 Span 和 SpanEvent 都包含一个 ServiceType,这个ServiceType表示跟踪方法所属的库,以及跟踪它的Span和spanevent应该如何处理。下表显示ServiceType包含哪些属性:
Pinpoint 为了尽量压缩 Agent 到 Collector 数据包的大小,ServiceType 被设计成不是以序列化字符串的形式发送的,而是以整形数字发送的 (code 字段),这就需要建立一个映射关系,将 code 转换成对应的 ServiceType 实例,这个映射机制就是由 TraceMetadataProvider 负责的。 ServiceType code必须是惟一的。如果要编写一个将被公开共享的插件,就必须联系pinpoint团队来获得分配的ServiceType code。如果所开发的插件是私有的,那可以从下面的表格中选择一个ServiceType code,如一会我们要展示的示例中一样。 公开的ServiceType Code范围私有的ServiceType Code范围
2.1.2、AnnotationKey
Annotation 是包含在 Span 和 SpanEvent 中的更详尽的数据,以键值对的形式存在,键就是AnnotationKey,值是基本类型,String或者byte[]。Pinpoint 内置了很多的 AnnotationKey,如果不够用的话也可以通过 TraceMetadataProvider 来自定义。AnnotationKey 的数据结构如下:
同 ServiceType 的 code 字段一样,AnnotationKey的 code 字段也是全局唯一的。如果开发的插件包含一个公开的AnnotationKey,就要联系pinpoint团队分配一个AnnotationKey code,如果是私有插件,那可以在900到999之间选择一个值作为code。
2.2、ProfilerPlugin
ProfilerPlugin修改目标库的类来收集跟踪数据。插件的工作原理:
2)注入拦截器来实际跟踪这些方法。
这些拦截器用于提取、存储和传递跟踪数据,然后将其发送给收集器。拦截器甚至可以相互协作,在它们之间共享上下文。插件还可以通过向目标类添加getter或定制字段来帮助跟踪,以便拦截器在执行期间可以访问它们。03
字节码注入怎么工作的由于字节码技术必须处理java字节码,它会增加开发的风险而降低生产效率。此外,研发人员容易犯错误。在Pinpoint中,通过拦截器抽象提高了生产力和可访问性。
Pinpoint中注入必要的代码,通过在类装入时插入应用程序代码来跟踪分布式事务和性能信息。由于跟踪代码直接注入到应用程序代码中,因此提高了性能。在 Pinpoint中,API截取和数据记录是分离的。如上图,拦截器被注入到我们希望跟踪的方法中,在该方法前后调用before()和after()处理数据记录。通过字节码指令, Pinpoint Agent可以仅从必要的方法记录数据,从而使分析数据的大小变得紧凑。下面就根据以上原理来实现一个插件,该插件能够拦截配置的方法。先定义些常量类型,设置ServiceType 和 相应的code,AnnotationKey和相应的code1public interface GeneralConfigConstants {2 //定义service type和code3ServiceType GENERAL_CONFIG_SERVICE_TYPE = ServiceTypeFactory.of(7510, "GENERAL_CONFIG", RECORD_STATISTICS);4 //定义annotation key 和 code5AnnotationKey GENERAL_CONFIG_RESULT = AnnotationKeyFactory.of(902, "general.config.result", AnnotationKeyProperty.VIEW_IN_RECORD_SET);6 String GENERAL_CONFIG_INTERCEPTOR = "com.navercorp.pinpoint.plugin.general.config.interceptor.GeneralConfigInterceptor";7 String PINPOINT_CONFIG_PATH = "/pinpoint/config/project.properties";8}
定义数据类型类用于读取配置文件中的配置,文件中的配置规则为
有参数方法package.Clazz.MethodArgs=arg1,arg2 无参数方法package.Clazz.MethodArgs每行一个配置项1public class ConfigInfo { 2 /** 3 * e.g. com.bestpay.demo.Clazz.MethodArgs=arg1,arg2 4 com.bestpay.demo.Clazz2.Method2 (no args) 5 */ 6 private String clazz; 7 private ListmethodArgs; 8 9 public String getClazz() {10 return clazz;11 }1213 public void setClazz(String clazz) {14 this.clazz = clazz;15 }1617 public List getMethodArgs() {18 return methodArgs;19 }2021 public void setMethodArgs(List methodArgs) {22 this.methodArgs = methodArgs;23 }24}25public class MethodArgs {26 private String method;27 private String[] args;2829 public String getMethod() {30 return method;31 }3233 public void setMethod(String method) {34 this.method = method;35 }36 public String[] getArgs() {37 return args;38 }39 public void setArgs(String[] args) {40 this.args = args;41 }42}
定义GeneralConfigMetadataProvider提供ServiceType元数据
1public class GeneralConfigMetadataProvider implements TraceMetadataProvider {2 @Override3 public void setup(TraceMetadataSetupContext context) {4 context.addServiceType(GeneralConfigConstants.GENERAL_CONFIG_SERVICE_TYPE);5context.addAnnotationKey(GeneralConfigConstants.GENERAL_CONFIG_ARGS);6 context.addAnnotationKey(GeneralConfigConstants.GENERAL_CONFIG_RESULT);7 }8}
定义针对方法的拦截器GeneralConfigInterceptor继承自SpanEventSimpleAroundInterceptorForPlugin,这里在方法执行后简单记录方法的名称,参数,返回值
1public class GeneralConfigInterceptor extends SpanEventSimpleAroundInterceptorForPlugin { 2 private final PLogger logger = PLoggerFactory.getLogger(this.getClass()); 3 public GeneralConfigInterceptor(TraceContext context, MethodDescriptor methodDescriptor){ 4 super(context, methodDescriptor); 5 } 6 7 @Override 8 protected void doInBeforeTrace(SpanEventRecorder recorder, Object target, Object[] args) { 9 }10 @Override11 protected void doInAfterTrace(SpanEventRecorder recorder, Object target, Object[] args, Object result, Throwable throwable) {12recorder.recordServiceType(GeneralConfigConstants.GENERAL_CONFIG_SERVICE_TYPE);13if (args != null && ArrayUtils.hasLength(args)){14 recorder.recordApi(getMethodDescriptor(), args);15 recorder.recordAttribute(GeneralConfigConstants.GENERAL_CONFIG_ARGS, args);16} else {17 recorder.recordApi(getMethodDescriptor());18}19recorder.recordAttribute(GeneralConfigConstants.GENERAL_CONFIG_RESULT, result);20recorder.recordException(throwable);21}
最后重要的是我们的插件,传入配置和转换模板transformTemplate
1public class GeneralConfigPlugin implements ProfilerPlugin, TransformTemplateAware{ 2 private final PLogger logger = PLoggerFactory.getLogger(this.getClass()); 3 private GeneralConfigConfiguration configuration; 4 private TransformTemplate transformTemplate; 5 @Override 6 public void setTransformTemplate(TransformTemplate transformTemplate) { 7 this.transformTemplate = transformTemplate; 8 } 9 @Override10 public void setup(ProfilerPluginSetupContext context) {11 configuration = new GeneralConfigConfiguration(context.getConfig());12 if (!configuration.isGeneralConfigEnabled()){13 return;14 }15 Listlist = PropertyLoader.propertiesToList(null);16 addTransformers(list);17 }1819 private void addTransformers(List configInfos){20 if (configInfos == null) {21 logger.error("No configuration for general config plugin");22return;23 }24 for (ConfigInfo configInfo : configInfos){25 addTransformer(configInfo);26 }27 }28 private void addTransformer(final ConfigInfo configInfo) {29 transformTemplate.transform(configInfo.getClazz(), new TransformCallback() {30 @Override31 public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {32 InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);33 List methodArgsList = configInfo.getMethodArgs();34 for (MethodArgs methodArgs : methodArgsList) {35 InstrumentMethod method = null;36//处理有参数方法 37if (methodArgs.getArgs() != null && methodArgs.getArgs().length > 0) {38 method = target.getDeclaredMethod(methodArgs.getMethod(), methodArgs.getArgs());39 } else {40//处理无参数方法41 method = target.getDeclaredMethod(methodArgs.getMethod());42 }43 method.addInterceptor(GeneralConfigConstants.GENERAL_CONFIG_INTERCEPTOR);44 }45 return target.toBytecode();46 }47 });48 }49}
META-INF/services目录下添加插件和元数据文件
1com.navercorp.pinpoint.plugin.ProfilerPlugin2--com.navercorp.pinpoint.plugin.general.config.GeneralConfigPlugin3com.navercorp.pinpoint.common.trace.TraceMetadataProvider4--com.navercorp.pinpoint.plugin.general.config.GeneralConfigMetadataProvider
当以上核心的代码写完后,将插件以jar的形式部署在准备好的agent目录中,启动项目,project.propertie中配置要拦截的方法如com.bestpay.middleware.service.StudentManagerImpl.getAllStudents。在请求的路径中能看到以下的信息。配置我们配置的方法被拦截到了。
至此,我们的小插件讲解就结束了,此为抛砖引玉,Pinpoint提供了丰富的插件开发 API,如拦截异步方法、调用链跟踪、拦截器之间共享数据等,有兴趣的同学可进一步探索。
Pinpoint是一款全链路分析工具,提供了无侵入式的调用链监控、方法执行详情查看、应用状态信息监控等功能。基于Google Dapper论文进行的实现。
原文发布时间为:2018-07-12
本文作者:吴振本文来自云栖社区合作伙伴“”,了解相关信息可以关注“”。转载地址:http://frovo.baihongyu.com/