pinpoint插件开发实践

简介: Pinpoint是一款全链路分析工具,提供了无侵入式的调用链监控、方法执行详情查看、应用状态信息监控等功能。基于Google Dapper论文进行的实现。

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, Logback

02
插件知识和相关数据结合
_
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.ProfilerPlugin
2META-INF/services/com.navercorp.pinpoint.common.trace.TraceMetadataProvider
  1. 介绍下几种关键的类
    2.1 TraceMetadataProvider

TraceMetadataProvider 实现类中提供ServiceTypes和AnnotationKeys。

2.1.1 ServiceTypes

每个 Span 和 SpanEvent 都包含一个 ServiceType,这个ServiceType表示跟踪方法所属的库,以及跟踪它的Span和spanevent应该如何处理。下表显示ServiceType包含哪些属性:

image


Pinpoint 为了尽量压缩 Agent 到 Collector 数据包的大小,ServiceType 被设计成不是以序列化字符串的形式发送的,而是以整形数字发送的 (code 字段),这就需要建立一个映射关系,将 code 转换成对应的 ServiceType 实例,这个映射机制就是由 TraceMetadataProvider 负责的。
ServiceType code必须是惟一的。如果要编写一个将被公开共享的插件,就必须联系pinpoint团队来获得分配的ServiceType code。如果所开发的插件是私有的,那可以从下面的表格中选择一个ServiceType code,如一会我们要展示的示例中一样。
公开的ServiceType Code范围

image

私有的ServiceType Code范围

image

2.1.2、AnnotationKey

Annotation 是包含在 Span 和 SpanEvent 中的更详尽的数据,以键值对的形式存在,键就是AnnotationKey,值是基本类型,String或者byte[]。Pinpoint 内置了很多的 AnnotationKey,如果不够用的话也可以通过 TraceMetadataProvider 来自定义。AnnotationKey 的数据结构如下:

image

同 ServiceType 的 code 字段一样,AnnotationKey的 code 字段也是全局唯一的。如果开发的插件包含一个公开的AnnotationKey,就要联系pinpoint团队分配一个AnnotationKey code,如果是私有插件,那可以在900到999之间选择一个值作为code。

image

2.2、ProfilerPlugin

ProfilerPlugin修改目标库的类来收集跟踪数据。插件的工作原理:

  1. Pinpoint Agent 随 JVM 一起启动
  2. Agent 加载所有 plugin 目录下的插件
  3. Agent 调用已加载的插件的 ProfilerPlugin.setup(ProfilerPluginSetupContext) 方法
  4. 在 setup 方法中,插件定义那些需要被转换的类并注册回调TransformerCallback
  5. 目标应用启动
  6. 每当类被加载的时候,Pinpoint Agent 会寻找注册到该类的回调 TransformerCallback
  7. 如果 TransformerCallback 被注册,Agent 就调用它的 doInTransform 方法
  8. TransformerCallback 修改目标类的字节码 (例如添加拦截器、字段等)
  9. 修改后的代码返回到 JVM,类被加载的时候使用修改后的字节码
  10. 应用程序继续
  11. 当调用到被修改的方法的时候,已注入的拦截器的 before 和 after 方法会被调用
  12. 拦截器记录追踪数据最重要的几点可以归结为
    1)找出哪些方法值得跟踪。

2)注入拦截器来实际跟踪这些方法。
这些拦截器用于提取、存储和传递跟踪数据,然后将其发送给收集器。拦截器甚至可以相互协作,在它们之间共享上下文。插件还可以通过向目标类添加getter或定制字段来帮助跟踪,以便拦截器在执行期间可以访问它们。

03
字节码注入怎么工作的

image

由于字节码技术必须处理java字节码,它会增加开发的风险而降低生产效率。此外,研发人员容易犯错误。在Pinpoint中,通过拦截器抽象提高了生产力和可访问性。
Pinpoint中注入必要的代码,通过在类装入时插入应用程序代码来跟踪分布式事务和性能信息。由于跟踪代码直接注入到应用程序代码中,因此提高了性能。
在 Pinpoint中,API截取和数据记录是分离的。如上图,拦截器被注入到我们希望跟踪的方法中,在该方法前后调用before()和after()处理数据记录。通过字节码指令, Pinpoint Agent可以仅从必要的方法记录数据,从而使分析数据的大小变得紧凑。
下面就根据以上原理来实现一个插件,该插件能够拦截配置的方法。
先定义些常量类型,设置ServiceType 和 相应的code,AnnotationKey和相应的code

1public interface GeneralConfigConstants {
2   //定义service type和code
3ServiceType GENERAL_CONFIG_SERVICE_TYPE = ServiceTypeFactory.of(7510, "GENERAL_CONFIG", RECORD_STATISTICS);
4   //定义annotation key 和 code
5AnnotationKey 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 List<MethodArgs> methodArgs;
 8
 9   public String getClazz() {
10       return clazz;
11   }
12
13   public void setClazz(String clazz) {
14       this.clazz = clazz;
15   }
16
17   public List<MethodArgs> getMethodArgs() {
18       return methodArgs;
19   }
20
21   public void setMethodArgs(List<MethodArgs> methodArgs) {
22       this.methodArgs = methodArgs;
23   }
24}
25public class MethodArgs {
26   private String method;
27   private String[] args;
28
29   public String getMethod() {
30       return method;
31   }
32
33   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   @Override
3   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   @Override
11   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   @Override
10   public void setup(ProfilerPluginSetupContext context) {
11       configuration = new GeneralConfigConfiguration(context.getConfig());
12       if (!configuration.isGeneralConfigEnabled()){
13           return;
14       }
15       List<ConfigInfo> list = PropertyLoader.propertiesToList(null);
16       addTransformers(list);
17   }
18
19   private void addTransformers(List<ConfigInfo> 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           @Override
31           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<MethodArgs> 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.ProfilerPlugin
2--com.navercorp.pinpoint.plugin.general.config.GeneralConfigPlugin
3com.navercorp.pinpoint.common.trace.TraceMetadataProvider
4--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
本文作者:吴振
本文来自云栖社区合作伙伴“中生代技术”,了解相关信息可以关注“中生代技术”。

相关文章
|
5月前
|
关系型数据库 API 数据库
盘点10个.NetCore实用的开源框架项目
盘点10个.NetCore实用的开源框架项目
271 0
盘点10个.NetCore实用的开源框架项目
|
3月前
|
缓存 编译器 Go
Build实战指南:优雅编译,高效开发
Build实战指南:优雅编译,高效开发
44 0
|
9月前
|
网络协议 Java 应用服务中间件
Docker实战 | 第二篇:IDEA集成Docker插件实现一键自动打包部署微服务项目,一劳永逸的技术手段值得一试
Docker实战 | 第二篇:IDEA集成Docker插件实现一键自动打包部署微服务项目,一劳永逸的技术手段值得一试
《阿里巴巴Java开发手册》IDEA插件使用,提升代码质量的利器
《阿里巴巴Java开发手册》IDEA插件使用,提升代码质量的利器
357 0
|
11月前
|
Java 数据安全/隐私保护 微服务
SpringBoot进阶:一键集成GrayLog,那叫一个丝滑
在微服务架构中,一个服务通常都会有多个实例,而这些服务实例可能会被部署到不同的机器或虚拟容器上。此时对于日志数据的查看和分析就会变得困难起来,因为这些服务的日志数据都散落在各自实例所在的机器或容器上。例如,我现在要在订单服务里查找一个订单id为1的日志,而订单服务有10个实例并且部署在10台不同的机器上,那么我就得一台台的去找这个日志数据。所以这时候我们就需要有一个可以实现日志聚合的工具,将所有实例的日志数据都聚合在一个地方,那么我们就不需要到每个实例去找日志了,而本文将使用的日志聚合工具为Graylog
|
Java
《阿里巴巴Java开发规约》插件使用详细指南
阿里巴巴于10月14日在杭州云栖大会上,正式发布众所期待的《阿里巴巴Java开发规约》扫描插件。今天就为大家详细介绍一下IDEA插件与Eclipse插件的安装使用。
14129 1
《阿里巴巴Java开发规约》插件使用详细指南
|
Web App开发 Java 测试技术
Gradle从0入门到实战系列【五】工程化之插件
插件可以封装一系列任务,例如 编译,测试,打包等。 IDEA、VsCode、Eclipse、Maven、Chrome等都是支持插件集成的工具。插件意味着扩展,Gradle只要定义好插件规范,各大厂商或个人开发者遵循这个规范就能开发出很多有用的插件,从而丰富Gradle生态。
151 0
Gradle从0入门到实战系列【五】工程化之插件
|
监控 Kubernetes Cloud Native
iLogtail社区版开发者指南 - 快速搭建编译和开发环境
想尝鲜最新版iLogtail代码提供的功能?想测试刚给iLogtail添加的代码?如何将iLogtail的开源代码转变为可执行程序是很多小伙伴关心的问题。本文将详细描述iLogtail的编译方法,并利用提供的开发镜像,详细描述如何构建一个高效开发iLogtail的环境。
766 0
|
监控 Java 中间件
Pinpoint实践
关于pinpoint的安装及使用网上有很多教程就不介绍了。这里主要记录下碰到的问题
Pinpoint实践
|
SQL 算法 Java
代码质量管理平台实战| SonarQube 安装、配置及 JaCoCo、Maven 集成
代码质量管理平台实战| SonarQube 安装、配置及 JaCoCo、Maven 集成