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-...

相关推荐
冬奇Lab7 小时前
每日一个开源项目(第133篇):EchoBird - 把 AI 工具的安装和部署做成傻瓜操作
人工智能·开源·资讯
NE_STOP10 小时前
Vide Coding--AI编程工具的选择
java
码云数智-园园11 小时前
C++20 Modules 模块详解
java·开发语言·spring
程序员黑豆11 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
霸道流氓气质11 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz11 小时前
Maven依赖冲突
java·服务器·maven
swordbob11 小时前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
咖啡八杯11 小时前
GoF设计模式——享元模式
java·spring·设计模式·享元模式
十五喵源码网12 小时前
基于springboot2+vue2的租房管理系统
java·毕业设计·springboot·论文笔记
摇滚侠12 小时前
IDEA 创建 Java 项目 手动整合 SSM 框架
java·ide·intellij-idea