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的入口,为什么这么说呢?

xml 复制代码
<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:

typescript 复制代码
    /**
     * 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的加载过程,此时会初始化所有的服务。也即我们看到的所有服务会在此时会做一个启动的操作,同时还会启动事件:

scss 复制代码
service.start();
collectServiceStartEvent(startServiceArray.toString());

其实这个两个方法也做了很多事情。

启动服务做的事情:

collectServiceStartEvent则调用netty客户端向netty服务端发送数据。到服务端后,服务端进行数据处理,其收集的信息提供给backend模块方便后台展示查看。

六、以标签路由为例PluginService中扩展插件初始化

除此之外,还有一批实现了BaseService接口的,也即PluginService扩展插件服务基类,以标签路由为例,可以看你的其初始化的整个过程。

七、install的过程

同时我们可以看到install对应的process方法也是执行它的方法:

scss 复制代码
  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的顺序。

ini 复制代码
 public static void init(Instrumentation instrumentation) {
        instrumentationCache = instrumentation;
        builder = BufferedAgentBuilder.build();

        // 初始化完成后,新增Action用于添加框架直接引入的字节码增强
        enhanceForFramework();
    }

执行下面的过程:

我们根据上面的添加顺序,来看初始化插件的顺序:

scss 复制代码
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这个接口。也即从这里可以看到也即自定义的插件:

php 复制代码
 /**
     * 从插件收集器中获取所有插件声明器
     *
     * @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官网: sermant.io/zh/

sermant开源地址:github.com/huaweicloud...

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

相关推荐
AI你一生一世4 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
云-缩影4 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
瑞克-桑切斯5 小时前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
wflynn5 小时前
GitHub 日榜趋势速报 | 2026-11-03
开源·github
冬奇Lab7 小时前
一天一个开源项目(第253篇):dsh-superagent —— DeepSeek 开源的“主动式”编程智能体原型,核心原则是少占人的注意力
人工智能·开源·资讯
字节跳动开源7 小时前
OpenViking 上线原生 3D 内容理解模型
数据库·开源
海的透彻9 小时前
dockerfile如何快速构建容器(java服务)
java·开发语言
code2cat9 小时前
Java工具篇之Gradle:用任务图组织构建与交付
java·开发语言·单元测试·gradle
車瑜91110 小时前
PM-Skills42个链式工作流-数据分析
开源·产品经理·pm·pm-skills
后台模板学习10 小时前
大模型 Prompt 工程在电商订单系统中的踩坑记录
java·python·prompt