Skywalking的增強與攔截機制


整理自架構經理(湯哥)的分享

字節增強條件匹配

在 skywalking 中實現很多基於 byte-buddy 的關於鏈式匹配查詢的實現, 代碼如下所示:

public abstract class AbstractJunction<V> implements ElementMatcher.Junction<V>

其對應的類的類圖關系如下所示:

除此之外, 為了便於更好的執行在攔截時期的的匹配, skywalking  又定義了一套自實現的 Match  的匹配器, 類的簡要類圖關系如下所示:

 

其中 NameMatch 名稱 Match 的 NameMatch 好象相對類的繼承相關較獨立,但是其它方式的 Match 繼承結構相對有點復雜, 都繼承於IndirectMatch , 上面類的簡意類圖對應的類的完整類圖詳情如下所示:

插件方式增強的擴展點

所有的基於 skywalking 的整套增強邏輯框架下,如查有新的要增強的中間件或組件, 必須遵守其制定的插件擴展規范, 其具體的規范如下:

一、 在對應的插件的 jar 包的 classpath 下必須存在定義文件 skywalking-plugin.def ,文件中必須包括 對 JTI 中的 Instrumentation 的自定義類的全限定名, 以 Dubbo 增強為例, 其內容為

dubbo=org.apache.skywalking.apm.plugin.dubbo.DubboInstrumentation

二、自定義的 Instrumentation 必須繼承至 ClassInstanceMethodsEnhancePluginDefine 抽象類 , 在類中,須指定這個設施的增強的兩個核心要素內容:

A). 須要增強的目標類 , Enhance Class
B). 插件中自己要實現的具體的攔截實現 xxxxxInterceptor
同樣以 Dubbo 為例, 如果要自定義自己的插件實的話,則就要指定如下內容:

三、最后就是要真實的定義一個 實現接口 InstanceMethodsAroundInterceptor  的一個攔截實現

對於每一個插件來說, 不一定只能有一個 Interceptor , 還可以有多個, 因為有時不一定只是要對一個類或一個方法進行攔截, 可能是多個類, 也有可能是一個類中的多個方法, 或多個類的多個方法,因此,在最初的 Instrumentation 入口定義之外,就提供了可以多個方法的增強擴展, 具體的如下圖針對 Dubbo 的增強所示:

插件方式增強主體邏輯

理解實現插件增強機理的類與類之間的關系,猶為重要,它也是 sky-walking 的對於中間件以插件方式進行增強操作的核心, 下面我們就來仔細的分析下,是如何進行自定義插件, 並如何進行對中間件的增強的。在整個的增強的機制中, ClassEnhancePluginDefine 是很重要, 很核心的一個類, 在其 enhance 方法中, 分別指定了對

  • 靜態方法增強
  • 實現方法增強
  • 定義了 ConstructorInterceptPoint、InstanceMethodsInterceptPoint、StaticMethodsInterceptPoint

這三個 構造器,實例方法, 靜態方法攔截點的 抽象 方法, 讓插件子類去實現攔截點

 下面是在 Dubbo  為例 DubboInstrumentation  插件主體實現依賴的類圖關系:

上面的是以 ClassEnhancePluginDefine 為核心的主體的核心類圖結構, 現在主要講一下此類的內部的主要構成與執行序列, 完整的類圖如下所示:

從上面可知, ClassEnhancePluginDefine 繼承至 AbstractClassEnhancePluginDefine , 做為所有的插件都要繼承實現的基礎, 抽象類AbstractClassEnhancePluginDefine 具體定義了什么行為, 這些行為又有哪些意義?什么地方在調用這個方法?

AbstractClassEnhancePluginDefine 中有四個方法

  • enhance 抽象方法, 抽象類中無實現, 子類去實現,具體的增強的邏輯
  • enhanceClass 抽象方法, 抽象類中無實現,子類去實現,具體的要增強的類的 Match
  • witnessClasses 也是可以被重載的一個方法,子類可重載
  • define 實例方法,它主要做了下面的幾次事 ( 入口方法)

1 找到增強插件類的所有顯式指定的 WitnessClasses - 用戶自己重載顯式指定
2 調用 enhance 方法, 執行真正的插件的增強邏輯,返回新的 DynamicType.Builder
3 設置上下文, 標記初始化定義步驟已完成
4 最后再返回新的 第 2 中的增強后的 newClassBuilder

那么在這個類中,最為重要的一個方法 define 又是誰調用的呢?

上面的就是這個類方法被使用的 Usages 情況, 主要是被 Sky-Walking Agent 的 Agent 主入口調用, 這個方法也是使用 byte-buddy 的字節碼增強的 agent 中的一個使用范式, 也就是說在 agent 執行真正的 transformer , 所有的插件中因為都是繼承至AbstractClassEnhancePluginDefine , 自然它們的 define 的定義的初始化方法, 也就會被全部調用初始化, 代碼如下所示:

 對於 snow-walking  來說, 使用 Agent  的固定范式( 來自於 byte-buddy  的固定實現方式)如下所示:

至此,已經分析完了, 所有 sky-walking  的自定義插件都必須要繼承並重載的抽象類 AbstractClassEnhancePluginDefine  , 它定義了所有的Agent 插件都必須要實現的行為, 以及它自身需要定義與初始化的行為邏輯。

接下為, 真正的主角上場了, 它就是 ClassEnhancePluginDefine  ,  其類的依賴關系如下:

 

 下面看一下 類的本身的方法, 總共有 六 個方法:

 enhance  方法, 重載了父類抽象類 AbstractClassEnhancePluginDefine  的方法, 做為所有插件的基類, 它定義了 enhance  方法必須所具備的操作。此方法的內部主要做了兩件事:

  • enhanceClass , 增強一個類,以攔截其靜態方法
  • enhanceInstance ,增強一個類,以攔截其構造器 與 實例方法

除了重載的方法外, 其它的三個方法, 分別提供了對應的攔截點:

 在實現自定義插件邏輯的時候, 要重載這三個方法中的實現, 以 Dubbo 插件攔截擴展為例, 在 DubboInstrumentation  中 就重載了 父類的getInstanceMethodsInterceptPoints方法, 以此來告訴父類中的共性 enhance  增強處理邏輯, 本插件的實例方法攔截點的一些元數據信, 區配的方法是什么,要增強的類是哪一個,是否要重載其方法參數,這些信息。

 上面這里, 在具體的 ClassEnhancePluginDefine  實現插件內部, 對於攔截器 intercepter  都是指定的是 字符串 String  類型, 那么父類中ClassEnhancePluginDefine 是如何轉化,並在什么時機調用這個 intercepter 的實現類, 以執行攔截的真實的邏輯的呢?

 那上面的這里從用戶自定義的插件中顯式的指定了 intercepter Fullname了,到了ClassEnhancePluginDefine后,進行enhance時,會最終都落在 byte-buddy  的 builder. method (....).intercpet (MethodDelegation.xxxx) 

這樣的固定增強的編程范式當中去。在 sky-walking 的實現中, 它又將 intercepter 的名稱, 根據不同的增強類型,傳入了不同類型的具體的委托執行實例當中去執行了。在 sky-walking 中, 根據增強的分類類型,委托執行實例分為以下幾種:

它們分別是, 靜態方法固定攔截委托實現器,實例方法固定攔截委托實現器,構造器固定攔截委托實現器, 它們只有一個主要的核心執行方法, 就是 intercept , 這個方法在執行時是與 byte-budy 的 MethodDelegation 配套使用的, 此方法必須要顯式的按規定指定相應的byte-buddy 的 Annotation 方能正常的執行增強工作。

因為這三個固定類型的攔截器的處理方式都是差不多, 這里就以 靜態方法攔截器舉例分析其內部的執行原理。先看一下這個攔截器的類
的依賴情況:

從上面的此攔截器的依賴情況可以, 它主要依賴兩個:

  • StaticMethodsAroundInterceptor , 這個接口的實現者是自定義插件自行根據需求實現的
  • InterceptorInstanceLoader ,這個是用戶自定義插件的攔截器的類加載器

之前我們說, 執行 byte-buddy 固定的 intercept 邏輯范式時, 通過 MethodDelegation 委托給了 sky-walking 的預設定的幾個類型的攔截器,在構建這些固定攔截器時, 傳入的都是用戶自定義的攔截器的 ClassFullName , 所以在真實的固定類型的攔截器內部,就得有一個機制去加載用戶自定義的攔截器,只有這樣, 這些攔截器才能被調用執行。
這些攔截器有三個固定的方法:

  • beforeMethod , 被攔截方法之前執行
  • afterMethod ,被攔截方法之后執行
  • handleMethodException ,處理捕獲執行期間的異常

OK , 分析到這里, 基本上, 用戶自定義增強插件,如何被增強,如何被攔截執行的過程應該算是比較清楚了。

增強與攔截機制總結

下圖是整理后的關於 sky-walking  的 APM 中用戶自定義插件的完整的調用鏈路, 它對於插件如何生效並進行增強與攔截的調用過程做了描繪。


免責聲明!

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



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