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具備了分布式跟蹤的能力。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM