写点什么

Sermant 的整体流程学习梳理

作者:华为云开源
  • 2024-02-29
    广东
  • 本文字数:3245 字

    阅读完需:约 11 分钟

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/Sermantbyte-buddy

开源地址:https://github.com/raphw/byte-buddy

用户头像

华为云开源官方博客--携手共建云原生根社区 2023-03-13 加入

还未添加个人简介

评论

发布
暂无评论
Sermant 的整体流程学习梳理_开源_华为云开源_InfoQ写作社区