Pinpoint 源码分析(一)

pinpoint 是什么?

这个问题还是详见官方文档,我写得应该不会比它更好了。

原文:https://github.com/naver/pinpoint/wiki/Technical-Overview-Of-Pinpoint

译文:https://github.com/skyao/learning-pinpoint/blob/master/design/technical_overview.md

论文:https://ai.google/research/pubs/pub36356

简单的说它可以用来追踪分布式系统的执行链路,记录执行时间、参数、异常等各种信息,可以作性能分析、监控预警、故障分析等用途。

假如读者已经对pinpoint有直观的认识,从githup download了相应源码,在本机编译运行quickstart应用,体验过了quickstart中pinpoint采集testApp接口调用数据,上报collect,在web中查看到了链路图,详细查看某个方法的执行链路。那么可以开始阅读本源码分析文章。

Interceptor 源码分析
如果已经阅读了pinpoint的介绍文章,那么就应该知道,pinpoint使用了java字节码增强技术ASM(ASM的详细信息可以自行google),配合java探针技术,可以在应用类装载前,改写类结构,比如增加字段、增加访问方法get/set、增加方法、改写方法。pinpoint就是通过这一系列技术达到对客户代码无侵入,用户使用pinpoint无需改写应用代码,无需增加锚点,pinpoint自动帮你完成一切。pinpoint的plugin就是完成对用户代码改写、增强的模块,pinpoint提供了很多内置插件,用户也可以按规范编写自己的插件。而我们马上要分析的Interceptor就是插件的基础,通过Interceptor来对方法做功能增强。

com.navercorp.pinpoint.bootstrap.interceptor 包下是拦截器相关的框架类,本文主要分析该包中的类。Interceptor 是其中的接口,它没有任何方法,是一个标记接口。
下面的AroundInterceptor、ApiIdAwareAroundInterceptor、StaticAroundInterceptor都继承自Interceptor。
AroundInterceptor

方法环绕拦截器,在方法执行的前后增加拦截,如果原方法体执行出现异常,也将被拦截

public interface AroundInterceptor extends Interceptor {

void before(Object target, Object[] args);

void after(Object target, Object[] args, Object result, Throwable throwable);
}
例:客户代码

public String say(String name){
//do something
return "hello "+name;
}
如果一个AroundInterceptor拦截以上方法,那么客户代码会被改写为如下,以下示例原理,不一定与实际实现一致。

public String say(String name){
AroundInterceptor interceptor = InterceptorRegistry.getInterceptor(1);
interceptor.before(this,new Object[]{name});
try {
//do something

Object result = "hello "+name;
interceptor.after(this,new Object[]{name},result,null);
return result;
} catch (Throwable t) {
interceptor.after(this,new Object[]{name},null,t);
throw t;
}
}
首先,在方法入口处会插入获取拦截器的代码,所有拦截器都注册在InterceptorRegistry中,每个注册的拦截器会被分配相应的id,此处通过id获取到相应的拦截器。原方法体被执行前先执行拦截器before方法,将当前对象及方法入参传入before。原方法体被try catch包裹,捕获所有原方法体的异常,如果有异常则先调用拦截器after方法,将异常信息传入,然后再原样抛出异常;如果方法正常返回,则所有返回语句都会被拦截,待after方法执行过后才返回。
这种改写技术,前面已经提到是ASM,这种技术要求开发人员对字节码要非常熟悉,一旦改写失败,那整个class都无法正常装载。如果将这种API暴露给插件开发人员,那对插件开发人员要求过高,这也就是为何pinpoint会抽象出Interceptor的原因,使用interceptor简化了改写字节码过程,用户只需要实现interceptor本身逻辑,改写字节码部分由pinpoint封装,执行期间会自动调用到用户编写的interceptor。

StaticAroundInterceptor

public interface StaticAroundInterceptor extends Interceptor {

void before(Object target, String className, String methodName, String parameterDescription, Object[] args);

void after(Object target, String className, String methodName, String parameterDescription, Object[] args, Object result, Throwable throwable);

}
静态方法的环绕拦截器,与AroundInterceptor类似,只不过是针对静态方法的拦截。

ApiIdAwareAroundInterceptor

public interface ApiIdAwareAroundInterceptor extends Interceptor {
void before(Object target, int apiId, Object[] args);
void after(Object target, int apiId, Object[] args, Object result, Throwable throwable);
}
关注apiId的环绕拦截器,拦截方法会多一个apiId。aware命名的接口在spring源码中也经常见到,表示实现类关注对某种目标,框架会把实现类关注的东西注入或方法参数传递给它。这里表示实现类对apiId关注。

com.navercorp.pinpoint.bootstrap.interceptor 包下还有 ExceptionHandler 接口

public interface ExceptionHandler {
void handleException(Throwable t);
}
这个接口用于处理拦截器方法发生的异常,我们插入了before、after方法,如果这些方法发生异常而不处理,那将影响到原始方法体的执行逻辑,因此有必要对拦截器方法发生的异常做处理。

ExceptionHandleAroundInterceptor 类,这是一个装饰器模式的类,用于装饰其它拦截器类,主要做异常处理的功能增强。
该包下还有各种以数字结尾的类,如:AroundInterceptor0、ExceptionHandleAroundInterceptor0等,其它包下也有类似的类,这些类都和不带数字的类功能类似,主要处理方法不同参数个数。

scope、ExecutionPolicy

下面再看scope包下的类,这里要解释的两个概念:scope、ExecutionPolicy。
scope 作用域或作用范围:它起到的作用是将多个拦截器关联在一起。
ExecutionPolicy 执行策略:它控制拦截器是否执行。

下面我通过一个示例说明pinpoint中为何要引入这样的两个概念,这两个概念又是如何解决相关的问题。

假如有2个方法a和b。a、b两个方法都要被pinpoint用于做分布式链路跟踪,因此a、b都会被改写并加上环绕拦截器。a方法在某些情况下有可能调用b方法,或a方法在某些情况下会递归调用自身,无论以上哪种情况,加在a、b方法上的拦截器都有可能重复执行,比如a方法内调用了b方法,那a和b的拦截器都要执行。有时候这种重复执行并不是我们想要的,我们只想a上的拦截器执行一遍就可以了,如果再调用b方法,b的拦截器不执行,如果递归调用a自身,那也不再执行。
所以这里就产生了一个需求,a和b的拦截器本来是独立的,现在需要有一种办法将他们关联起来,需要有个一类似上下文的记录,比如a拦截器已经执行了,b的拦截器能感知到,然后不执行。或a递归时,能感知到已经在递归了,不重复执行拦截器。解决这个需求的关键就是通过scope,同名scope可以将拦截器关联,记录上下文信息,并决定拦截器是否被执行。
再回到上面的a、b方法,我们之前的需求是只想让拦截器执行一次,假如我们的确有需求,要记录每一次的执行情况呢?或者我们的需求是只第一次不执行,后面的每次都执行?
ExecutionPolicy 就是为解决我们上述两种需求的,下面我们通过源码来分析。

scope包下,我们可以看到InterceptorScope和InterceptorScopeInvocation,还有一个ExecutionPolicy枚举类。

public interface InterceptorScope {
String getName();
InterceptorScopeInvocation getCurrentInvocation();
}

public interface InterceptorScopeInvocation {
String getName();

boolean tryEnter(ExecutionPolicy policy);//以给定执行策略尝试进入拦截器
boolean canLeave(ExecutionPolicy policy);
void leave(ExecutionPolicy policy);

boolean isActive();

Object setAttachment(Object attachment);//同scope的拦截器之间可以传递参数
Object getAttachment();
Object getOrCreateAttachment(AttachmentFactory factory);
Object removeAttachment();
}

public enum ExecutionPolicy {
ALWAYS, //总是执行拦截器
BOUNDARY, //边界处执行拦截器
INTERNAL //与BOUNDARY相反,边界处不执行,内部调用时执行拦截器
}
通过以上接口的设计,我们可以看出以下信息
scope 是通过名字进行标识,名字相同scope就相同。
scope 可以获取到 InterceptorScopeInvocation
InterceptorScopeInvocation 中包含拦截器执行与否的控制逻辑,这个控制逻辑可以有
ExecutionPolicy 来指定。
拦截器之间可以通过 scope 传递参数。

在自定义拦截器时,如何获取 InterceptorScope 呢?只需要在拦截器构造函数中,将
InterceptorScope 作为参数传入即可。自定义拦截器的初始化时交给框架的,框架会解析拦截器构造函数的参数,并传入相应的值。

比如:

com.navercorp.pinpoint.plugin.httpclient3.interceptor.HttpMethodBaseExecuteMethodInterceptor
构造函数:

public HttpMethodBaseExecuteMethodInterceptor(TraceContext traceContext, MethodDescriptor methodDescriptor, InterceptorScope interceptorScope)
1
该拦截器在 com.navercorp.pinpoint.plugin.httpclient3.HttpClient3Plugin 被添加到指定方法,代码如下

InstrumentMethod execute = target.getDeclaredMethod(“execute”, “org.apache.commons.httpclient.HttpState”, “org.apache.commons.httpclient.HttpConnection”);
if (execute != null) {
execute.addScopedInterceptor(HttpMethodBaseExecuteMethodInterceptor.class, HttpClient3Constants.HTTP_CLIENT3_METHOD_BASE_SCOPE, ExecutionPolicy.ALWAYS);
}
自定义拦截器会被com.navercorp.pinpoint.profiler.objectfactory.AutoBindingObjectFactory 的工厂方法初始化,

Interceptor interceptor = (Interceptor) factory.createInstance(interceptorClass, providedArguments, interceptorArgumentProvider);
初始化的过程中,会通过拦截器的构造函数,先解析构造函数的参数,然后注入相应的值。熟悉springMvc的应该很清楚这种方式,springMvc中的controller方法,也是通过这种形式自动注入参数的。因此,如果自定义拦截器依赖 InterceptorScope ,那么相应的值会自动注入。 InterceptorScope 在pinpoint中有默认的实现:请见profile模块中com.navercorp.pinpoint.profiler.interceptor.scope 包下的两个类 DefaultInterceptorScope 和 DefaultInterceptorScopeInvocation。

初始化自定义拦截器之后,自定义拦截器会被装饰,作功能增强,

com.navercorp.pinpoint.profiler.interceptor.factory.AnnotatedInterceptorFactory 的

public Interceptor newInterceptor(Class<?> interceptorClass, Object[] providedArguments, ScopeInfo scopeInfo, InstrumentClass target, InstrumentMethod targetMethod)
方法返回值
wrap(interceptor, scopeInfo, interceptorScope);

装饰器就是
com.navercorp.pinpoint.bootstrap.interceptor.scope 包下的 相关类。

至此,我们基本分析完了pinpoint 的 interceptor ,这是pinpoint无侵入实现的基础。下一篇文章我们看看通过拦截器,pinpoint为应用增加了什么代码,使得pinpoint具备了分布式跟踪的能力。

猜你喜欢

转载自www.cnblogs.com/yikemm/p/10383788.html