本文分享自華為雲社群《Sermant 的整體流程學習梳理》,作者:用友汽車資訊科技(上海)有限公司 劉亞洲 Java研發工程師。
一、sermant架構
Sermant整體架構包括Sermant Agent、Sermant Backend、Sermant Injector、動態配置中心等元件。其中Sermant Agent是提供位元組碼增強基礎能力及各類服務治理能力的核心元件,Sermant Backend、Sermant Injector、動態配置中心為Sermant提供其他能力的配套元件。
二、java agent和bytebuddy組合使用場景
比較典型的就是skywalking、sermant、arthas、mockito。如果說java agent開了一扇門,那麼bytebuddy在開的這扇門中開啟了一片新的天地。
三、Sermant的入口
前面我們說AgentLauncher是java agent的入口,為什麼這麼說呢?
<manifestEntries> <Premain-Class>com.huaweicloud.sermant.premain.AgentLauncher</Premain-Class> <Agent-Class>com.huaweicloud.sermant.premain.AgentLauncher</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries>
答案可以從pom.xml中找到答案,這裡可以看到基於Premain-Class和Agent-Class的兩個類都指向了AgentLauncher這個類。因此我們可以非常確認的肯定它就是javaagent入口類。類似於java程式有一個main的執行入口,而java agent有一個自己的入口類premain。
因此可以看到它的入口執行main:
/** * premain * * @param agentArgs premain啟動時攜帶的引數 * @param instrumentation 本次啟動使用的instrumentation */ public static void premain(String agentArgs, Instrumentation instrumentation) { launchAgent(agentArgs, instrumentation, false); } /** * agentmain * * @param agentArgs agentmain啟動時攜帶的引數 * @param instrumentation 本次啟動使用的instrumentation */ public static void agentmain(String agentArgs, Instrumentation instrumentation) { launchAgent(agentArgs, instrumentation, true); }
基於premain模式的和基於agent模式,區別在於是否為isDynamic。從這裡我們可以看到這裡提出了兩個類值得我們去關注:AgentCoreEntrance、CommandProcessor,也即sermant這個專案的兩個重點類。
更多需要了解的,可以參考byte-buddy這個開源專案。
四、入口方法執行的全流程
五、spi的載入過程
啟動核心服務的過程是spi的載入過程,此時會初始化所有的服務。也即我們看到的所有服務會在此時會做一個啟動的操作,同時還會啟動事件:
service.start();
collectServiceStartEvent(startServiceArray.toString());
其實這個兩個方法也做了很多事情。
啟動服務做的事情:
collectServiceStartEvent則呼叫netty客戶端向netty服務端傳送資料。到服務端後,服務端進行資料處理,其收集的資訊提供給backend模組方便後臺展示檢視。
六、以標籤路由為例PluginService中擴充套件外掛初始化
除此之外,還有一批實現了BaseService介面的,也即PluginService擴充套件外掛服務基類,以標籤路由為例,可以看你的其初始化的整個過程。
七、install的過程
同時我們可以看到install對應的process方法也是執行它的方法:
public ResettableClassFileTransformer install(Instrumentation instrumentation) { AgentBuilder builder = new Default().disableClassFormatChanges(); // 遍歷actions for (BuilderAction action : actions) { builder = action.process(builder); } // 執行安裝操作,此時交給bytebuddy return builder.installOn(instrumentation); }
從入參中的Instrumentation,我們往回看:ByteEnhanceManager.init(instrumentation)
這個方法裡面定義了action的順序。
public static void init(Instrumentation instrumentation) { instrumentationCache = instrumentation; builder = BufferedAgentBuilder.build(); // 初始化完成後,新增Action用於新增框架直接引入的位元組碼增強 enhanceForFramework(); }
執行下面的過程:
我們根據上面的新增順序,來看初始化外掛的順序:
public static void enhanceDynamicPlugin(Plugin plugin) { if (!plugin.isDynamic()) { return; } // 獲取描述資訊 List<PluginDescription> plugins = PluginCollector.getDescriptions(plugin); // 新增外掛,然後執行安裝操作 ResettableClassFileTransformer resettableClassFileTransformer = BufferedAgentBuilder.build() .addPlugins(plugins).install(instrumentationCache); plugin.setClassFileTransformer(resettableClassFileTransformer); }
從引用上看,PluginSystemEntrance.initialize(isDynamic)中引用了這個方法。
可以看到這裡的新增外掛,可以理解為自定義的外掛。
從sermant官網,我們可以知道:定義自定義外掛,需要實現PluginDeclarer這個介面。也即從這裡可以看到也即自定義的外掛:
/** * 從外掛收集器中獲取所有外掛宣告器 * * @param classLoader 類載入器 * @return 外掛宣告器集 */ private static List<? extends PluginDeclarer> getDeclarers(ClassLoader classLoader) { final List<PluginDeclarer> declares = new ArrayList<>(); for (PluginDeclarer declarer : loadDeclarers(classLoader)) { if (declarer.isEnabled()) { declares.add(declarer); } } return declares; }
有了外掛,就可以進行安裝操作。
按照這個順序,可以看到對應的action.process(builder)裡面也執行了對應的構建方法。完成構建後,執行installOn方法。
完成安裝工作後,根據安裝前spi的增強實現,然後執行下游服務攔截增強,從而實現精準篩選工作。
八、以標籤路由下游攔截處理為例
可以看到標籤路由對應的幾個代表性的Declarer:
NopInstanceFilterDeclarer、LoadBalancerDeclarer、BaseLoadBalancerDeclarer、ServiceInstanceListSupplierDeclarer等。
對應的攔截器Interceptor:
NopInstanceFilterInterceptor、LoadBalancerInterceptor、BaseLoadBalancerInterceptor、ServiceInstanceListSupplierInterceptor。
兩者相互照應。
LaneServiceImpl和LoadBalancerServiceImpl是基於sermant框架的外掛服務spi做的實現。
LaneServiceImpl和RouteRequestTagHandler是和路由能力相關的,LaneServiceImpl和LaneRequestTagHandler是和染色能力相關的。
RouteRequestTagHandler用來攔截並儲存呼叫過程中的標籤,FlowRouteHandler和TagRouteHandler是在路由選擇下游例項時做的篩選過程。
下游攔截方法會經過BaseLoadBalancerInterceptor到loadBalancerService.getTargetInstances(serviceId, instances, requestData),最終到 HandlerChainEntry.INSTANCE.process(targetName, instances, requestData),基於責任鏈模式執行處理。目前主要有兩種方式:FlowRouteHandler和TagRouteHandler。
這裡面只是簡單的介紹了整體的流程,具體細節的內容,還需要自己多實踐。同時sermant大量使用了java agent的內容。
由於本人的侷限性,有不妥的地方,還望批評指正!
參考:
- sermant官網: https://sermant.io/zh/
- sermant開源地址:https://github.com/huaweicloud/Sermant
- byte-buddy開源地址:https://github.com/raphw/byte-buddy
點選關注,第一時間瞭解華為雲新鮮技術~