写点什么

开发者能力机制解析,玩转 Sermant 开发

作者:华为云开源
  • 2023-12-18
    广东
  • 本文字数:5154 字

    阅读完需:约 17 分钟

 

作者:栾文飞      华为云高级软件工程师

前言:

《Sermant框架下的服务治理插件快速开发及使用指南》中带大家一起体验了 Sermant 插件的开发,快速的了解了 Sermant 插件开发的全过程,本着从入门到精通的思路,本文对在开发中所常用的能力,从机制上进行更深入的解析。

插件加载 &插件调度

解析插件的加载和调度前,可以再回顾一下,Sermant 作为一个基于 Java 字节码增强技术的插件化服务网格,在设计之初就为插件设计了完整的类隔离机制,在《Sermant类隔离架构解析——解决JavaAgent场景类冲突的实践》中进行的详尽的介绍和分析,避免让开发者陷入到复杂的类冲突问题中,从开发者视角来看,可以无需关注类冲突问题,也对 Sermant 的类隔离机制无感知,同时借助 Sermant 的局部类加载机制,可以更建议的开发出高性能的服务治理插件。


点击并拖拽以移动

编辑

图- Sermant 类隔离机制

插件加载

既然是开发 Sermant 插件,最先应该了解的是插件是如何加载和调度的,Sermant 的插件化机制中得益于 Java 的 SPI 机制,在很多高可扩展的项目中,都会利用 SPI 去加载自己的扩展,常见的使用 SPI 机制的场景包括日志框架、数据库驱动、序列化工具、缓存框架等。

Java SPI(Service Provider Interface)是 Java 提供的一种服务提供者接口,用于在运行时动态加载实现某个接口或者抽象类的类。通过 SPI 机制,提供实现的一方可以将自己的实现以插件的形式注入到系统中,而无需修改原有的代码。SPI 机制是 Java 中一种基于接口编程的思想,它提高了代码的可扩展性和灵活性,使得应用程序更加易于扩展和维护。


点击并拖拽以移动

编辑

图- Sermant SPI 加载机制

在 SPI 中有三个关键的要素——接口定义、实现创建、配置文件。在 Sermant 中,框架中定义插件声明接口用于让插件开发者来定义插件的核心要素,插件开发者只需要按照接口契约,创建自身所需的插件声明实现即可,插件声明接口定义如下:

public interface PluginDeclarer {   /**      * 获取插件的类匹配器      *      * @return 类匹配器      */     ClassMatcher getClassMatcher();  
/** * 获取插件的拦截声明 * * @param classLoader 被增强类的类加载器 * @return 拦截声明集 */ InterceptDeclarer[] getInterceptDeclarers(ClassLoader classLoader);
/** * 由插件声明器决定是否需要增强被拦截的方法,默认为true * * @return 加载与否 */ default boolean isEnabled() { return true; } }
复制代码


点击并拖拽以移动

至于 SPI 机制中的另一个核心要素——配置文件,则需要开发者在插件声明实现创建完成后资源目录 resources 中添加 META-INF/services 目录,并在其中创建名为 com.huaweicloud.sermant.core.plugin.agent.declarer.PluginDeclarer 的 SPI 文件,并向其中添加插件声明实现的类名,这样再接入 Sermant 时,Sermant 就可以按照配置的指定来将对应的插件声明加载起来。

插件调度

仅依赖 SPI 机制是无法支持 Sermant 强大的框架能力的,在 Sermant 体系中,每一种不同的治理能力都是一个独立的插件,并且每个服务治理能力的实现,都依赖于多个插件声明的组合和拦截器的组合,通俗来讲,每个插件中都依赖了多个字节码切面来完成完整的服务治理,多插件难免会出现使用相同的切点来执行字节码增强逻辑。在如此情况下,Sermant 该如何保证各插件的执行顺序,并且保证不会重复的进行字节码的织入呢。

Sermant 在最底层维护了一个切面的调度器,首先在插件加载的过程中,调度器会将插件的拦截器(Sermant 中定义服务治理逻辑的组件)通过有序列表进行缓存,当字节码织入点被触发时,会进行拦截器的调度,此时 Sermant 将模仿方法堆栈的执行方式,先进入的方法后结束:


点击并拖拽以移动

编辑

图- Sermant 插件调度逻辑

当在进入目标方法时,调度器将对拦截器按照插件加载的方式执行,这样保证在进入方法时的运行顺序符合方法运行的规律;当执行出目标方法时,调度器将对拦截器按照插件加载顺序的逆序执行,这服务方法堆栈中,方法结束的规律。

基于上述逻辑,通过调度器这一层,可以保证不会对相同目标类目标方法进行重复的无意义的字节码增强,同时保证插件在相同目标的执行逻辑符合方法调用堆栈的逻辑,更符合切面程序的执行风格。并且可以通过控制插件来达到控制拦截器执行顺序的目的,也就是达到了控制插件顺序来控制服务治理生效时机的作用,这对一些特殊场景大有裨益。

开发者相关能力解析

除了插件化和类加载等框架的核心机制,插件开发者更多的需要了解 Sermant 所提供的一些开发这能力,只有更深入的了解这些能力,才能在服务治理插件开发的时候信手拈来。 插件简单来讲就是一系列切面的集合,最终完成了复杂的治理能力。在面向切面编程时,有两个核心的概念,即 Join point(切点)——指定切面的横切位置;Advice(通知)——切面执行的具体行为。对应 Sermant 的插件开发中也有逻辑与之对应,在 Sermant 中声明切面位置的称之为插件声明,执行切面逻辑的称之为拦截器。

Sermant 的插件声明可以基于类名、超类、注解等进行类定位,并通过方法名、类型、参数、返回值等进行方法定位,通过丰富的类匹配能力和方法匹配能力,可以更容易的指定自己期望的织入点。

Sermant 的拦截器提供了 Before、After、Throw 三个关键的生命周期,并在其上提供了形如跳过方法执行修改方法参数修改方法返回修改异常抛出等通用能力。


点击并拖拽以移动

编辑

图- Sermant 拦截器提供的能力

拦截器的 Before 逻辑将会被 Sermant 的切面调度器在方法执行前按照插件的加载顺序进行调度,这里我们可以通过 Sermant 提供的 API 来终止方法的执行,并且可以获取到当前拦截的对象的相关信息,并且还可以获取和修改方法的入参,这里就要注意了,修改入参可能会被其他插件所感知,这里就体现了切面调度器的重要性,如果修改参数产生了预期外的影响,可以通过调整插件顺序的方式来避免这种影响。

插件的 After 和 Throw 逻辑将会在目标方法执行结束时,被 Sermant 的切面调度器统一按照插件加载顺序的逆序进行调度,在此时我们还可以再次来修改方法的返回值和异常。在 After 中如果需要修改方法的返回值,则也同 Before 逻辑一样,需要注意拦截器的执行顺序,如果产生了预期外的影响,可以尝试通过调整插件顺序来进行避免。

在 Throw 逻辑中,只有当方法抛出异常时,Sermant 才能触发拦截器处理 Throw 逻辑,如果异常在方法中被捕获,则无法触发 Throw 的拦截器处理逻辑,如果在 Throw 逻辑中将异常修改为 null,此时方法将不再会抛出异常。

统一动态配置

《如何利用动态配置中心在JavaAgent中实现微服务的多样化治理》中,已经对动态配置进行了详细的介绍,本文就不再进行详细的叙述,Sermant 动态配置模型是一种基于分层模型设计的配置管理方案,它的核心组件包括 Group 和 Key。Sermant 通过不同的 Group(分组信息)来对配置项进行隔离,使配置管理更具灵活性和可扩展性;同时,通过 Key 对配置项进行具体属性的标识,实现了对配置项的精准控制和高效维护,其在主流的配置中心中的概念对应关系如下:


点击并拖拽以移动

编辑

图- Sermant 统一动态配置相关概念

Sermant 为开发者和使用者屏蔽了配置中心的差异,可以无需修改任何代码,就可以让 Sermant 对接多种配置中心,开发者只需要通过 Group 和 Key 进行配置的划分,无需了解各配置中心的实际字段,就可以开发出不依赖配置中心的动态服务治理能力。

统一日志解析

日志是在程序开发中不可或缺的能力,通过日志可以快速找出程序运行时的状态及遇到的问题。Sermant 的日志有两个很重要诉求,第一个就是需要隔离,避免 Sermant 日志系统对微服务带来不良的影响,例如破坏了微服务的日志配置,Sermant 日志和微服务日志交叉输出,影响微服务日志检索定位问题。第二个就是需要有监控能力,可以高性能的将执行过程中的异常信息通过 Sermant Backend 可观测,及时发现边车运行的异常问题。

Sermant 的统一日志,首先 Sermant 框架通过自定义的类加载器将日志引擎和微服务的日志引擎进行隔离,这样避免共用日志引擎,并且限制日志引擎的资源加载只在 Sermant 自定义的类加载器中进行:

@Override  public Enumeration<URL> getResources(String name) throws IOException {      // 由于类隔离的原因针对StaticLoggerBinder不再通过父类加载器获取重复资源,只返回加载器内的资源      if ("org/slf4j/impl/StaticLoggerBinder.class".equals(name)) {          return findResources(name);      }      return super.getResources(name);  }  
复制代码


点击并拖拽以移动

第二步 Sermant 框架通过自定义类加载器来限制日志引擎所能加载到的配置,通过限制"logback.xml"文件资源的加载,来限制日志的配置:

@Override  public URL getResource(String name) {      URL url = null;        // 针对日志配置文件,定制化getResource方法,获取FrameworkClassloader下资源文件中的logback.xml      if (CommonConstant.LOG_SETTING_FILE_NAME.equals(name)) {          File logSettingFile = BootArgsIndexer.getLogSettingFile();          if (logSettingFile.exists() && logSettingFile.isFile()) {              try {                  url = logSettingFile.toURI().toURL();              } catch (MalformedURLException e) {                  url = findResource(name);              }          } else {              url = findResource(name);          }      }      if (url == null) {          url = super.getResource(name);      }      return url;  }  
复制代码


点击并拖拽以移动

最后通过 JUL 桥接日志,借助于jul-to-slf4j (opens new window)的桥接能力将 JUL 日志桥接到日志引擎。最终,Sermant 开发者在使用统一日志时,通过 JUL 接口来构造日志即可,无需再依赖其他第三方日志门面依赖,仅需使用 Java 原生日志接口,日志和微服务完全隔离的,避免了边车日志系统对微服务日志系统带来不良的影响。


图- Sermant 日志系统

除此之外,Sermant 中改造了日志处理器,通过包装日志的桥接处理器,在高级别日志构造时通过 Sermant 的事件系统进行监控:

public class SermantBridgeHandler extends SLF4JBridgeHandler {      @Override      protected void callLocationAwareLogger(LocationAwareLogger lal, LogRecord record) {          // 覆写SLF4JBridgeHandler的日志转换方法,上报日志事件          int julLevelValue = record.getLevel().intValue();            if (julLevelValue > Level.INFO.intValue() && julLevelValue <= Level.WARNING.intValue()) {              // 记录警告级别日志              LogEventCollector.getInstance().offerWarning(record);          } else if (julLevelValue > Level.WARNING.intValue()) {              // 记录错误级别日志              LogEventCollector.getInstance().offerError(record);          }          super.callLocationAwareLogger(lal, record);      }  }  
复制代码


针对高级别的日志进行监控,可以通过配置事件系统将高级别日志进行异常的上报,通过 Sermant Backend 可以第一时间发现边车运行的异常状态。

结语

本文针对 Sermant 插件开发中的总会接触到的一些能力进行了更深层次的解析,基于更深入的了解,在插件开发时,才能更灵活的使用 Sermant 提供的丰富开发者能力,希望本篇文章可以对广大插件开发者带来一定的启发,除了上述能力,在插件开发中还可能需要用到利用 Archetype 能力快速构建项目并使用如心跳链路标记等加速服务治理的开发,如何构建局部类加载环境等更多的开发指导可见Sermant开发者指南

开发完成后,如想在 k8s 环境下快速部署 Sermant动态的执行 Sermant 的安装和卸载重复安装插件以及完成边车的自监控等,可通过Sermant用户使用手册学习更多技巧。

-----------------------------------------------------------------------------------------

Sermant 作为专注于服务治理领域的字节码增强框架,致力于提供高性能、可扩展、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、功能、体验的看护,广泛欢迎大家的加入。



用户头像

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

还未添加个人简介

评论

发布
暂无评论
开发者能力机制解析,玩转Sermant开发_华为云开源_InfoQ写作社区