Sermant運行流程學習筆記,速來抄作業

本文分享自華爲雲社區《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

 

點擊關注,第一時間瞭解華爲雲新鮮技術~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章