写点什么

Sermant 热插拔能力在故障注入场景的实践

作者:华为云开源
  • 2024-04-07
    广东
  • 本文字数:4958 字

    阅读完需:约 16 分钟

Sermant热插拔能力在故障注入场景的实践

作者:张豪鹏 华为云高级软件工程师

一、     前言

Sermant是基于 Java 字节码增强技术的无代理服务网格,采用 Java 字节码增强技术为宿主应用程序提供服务治理功能。从 1.2.0 版本开始,Sermant 已经实现了在服务不停机状态下进行安装和卸载的热插拔功能,在上一篇文章《服务运行时动态挂载JavaAgent和插件——Sermant热插拔能力解析》中已经介绍了 Sermant 热插拔功能的实现原理。本篇文章将通过故障注入场景,来展示 Sermant 热插拔能力的应用价值。

二、     故障注入

1)      什么是故障注入?

故障注入是一种测试方法,它通过在系统中故意引入错误或故障,来测试系统对这些错误或故障的响应和恢复能力,并验证系统是否能够正常处理这些异常情况。下图是故障注入测试中的一些常见故障:

通过故障注入技术,测试人员可以在 Java 应用中模拟各种故障场景,以便评估应用的响应能力和恢复能力,还可以帮助提前拦截和发现 Java 应用潜在的可靠性问题,提升应用稳定性,避免现网出现重大质量事故。例如:

  • 通过在指定方法中抛出自定义异常可以测试系统在异常情况下的稳定性

  • 通过修改指定方法的返回值可以测试系统在异常数据情况下的处理能力

  • 通过数据库故障可以测试系统在异常情况下是否可以保持数据一致性等。

2)      Java 应用实现故障注入方案的难点是什么?

传统的故障注入方案是在应用中通过手动或者脚本的方式来引入故障,例如:修改代码、改变输入值、随机错误注入等。传统方式在进行故障注入测试时会存在以下问题:

  • 通过修改代码来注入故障时,每次注入新的故障都需要进行代码修改并重启服务,影响故障注入的效率

  • 通过开关配置来进行故障注入时,需要增加大量的开关逻辑判断


因此如何在不重启应用的情况下实现故障注入,并且可以重复的进行故障注入对提高故障注入测试的效率和全面性变得至关重要。

三、     Sermant 热插拔功能在故障注入场景下的应用

Sermant 热插拔功能可以在服务不停机状态下进行故障注入插件的安装和卸载,故障注入插件在插件安装的时候可以指定任意方法进行故障注入,在故障注入测试完成后可以卸载该插件来避免影响系统运行,故障注入插件卸载完成后可以重新安装故障注入插入进行其他方法的故障注入。

1)      Sermant 热插拔功能是什么?

Sermant 热插拔功能是基于 JavaAgent 动态加载机制实现的,可以在服务不停机状态下进行 Java Agent 和插件的安装、卸载,而且安装、卸载插件时不会影响其他插件的正常运行。


下图为 Sermant 热插拔能力的示意图,Sermant 可以在服务运行过程中进行 Agent 动态安装,Agent 安装完成后可以通过动态安装、卸载插件来调整所需的微服务治理能力,也可以卸载整个 Agent。


2)      基于 Sermant 实现故障注入插件

Sermant 插件主要通过实现以下接口来实现字节码增强的功能:

  1. 通过实现 PluginConfig 来定义插件需要的配置。

  2. 通过实现 AbstractPluginDeclarer 来声明进行字节码增强的方法,类似于面向切面编程中的 Joint point。

  3. 通过实现 AbstractInterceptor 来定义拦截器,类似于面向切面编程中的 Advice。

Sermant 插件的详细介绍请参考文章《开发者能力机制解析,玩转Sermant开发》

故障注入插件可以在 PluginConfig 实现类中接受 Sermant 动态安装时传递的参数信息。(基于 Sermant 热插拔功能进行动态安装时,可以通过 Java Attach API 传输的参数来设置 PluginConfig 实现类的属性值)。下面为配置类 FaultInjectConfig 的代码实现:

@ConfigTypeKey("fault")  public class FaultInjectConfig implements PluginConfig {      private String className;        private String methodName;        private String exceptionName;        public String getClassName() {          return className;      }        public void setClassName(String className) {          this.className = className;      }        public String getMethodName() {          return methodName;      }        public void setMethodName(String methodName) {          this.methodName = methodName;      }        public String getExceptionName() {          return exceptionName;      }        public void setExceptionName(String exceptionName) {          this.exceptionName = exceptionName;      }  } 
复制代码

故障注入插件可以在 AbstractPluginDeclarer 实现类中声明进行字节码增强的类和方法,即进行故障注入的类和方法。结合配置类 FaultInjectConfig,故障注入插件可以在 Sermant 动态安装时调整故障注入的类和方法。如下面代码块所示(下面为故障注入声明器 FaultInjectDeclarer 的代码,基于配置类 FaultInjectConfig 声明故障注入的类和方法):

public class FaultInjectDeclarer extends AbstractPluginDeclarer {  
private FaultInjectConfig faultInjectConfig = PluginConfigManager.getPluginConfig(FaultInjectConfig.class); // 通过faultInjectConfig的className匹配需要进行字节码增强的类 @Override public ClassMatcher getClassMatcher() { return ClassMatcher.nameEquals(faultInjectConfig.getClassName()); } // 拦截声明,通过faultInjectConfig的methodName匹配需要进行增强的方法,并声明拦截器为CustomExceptionInterceptor @Override public InterceptDeclarer[] getInterceptDeclarers(ClassLoader classLoader) { return new InterceptDeclarer[]{ InterceptDeclarer.build(MethodMatcher.nameEquals(faultInjectConfig.getMethodName()), new CustomExceptionInterceptor())}; } }
复制代码

故障注入插件需要在 AbstractInterceptor 的实现类中定义字节码增强的逻辑,即进行故障注入,下面代码块为实现在方法执行前抛出自定义异常的故障注入逻辑:

public class CustomExceptionInterceptor extends AbstractInterceptor {      private FaultInjectConfig faultInjectConfig = PluginConfigManager.getPluginConfig(FaultInjectConfig.class);        @Override      public ExecuteContext before(ExecuteContext context) throws Exception {          // 实例化需要抛出的异常,并设置          Exception exception = (Exception) Class.forName(faultInjectConfig.getExceptionName())                  .getConstructor(null).newInstance(null);          context.setThrowableOut(exception);            // 设置跳过方法原有执行逻辑          context.skip(new Object());          return context;      }        @Override      public ExecuteContext after(ExecuteContext context) throws Exception {          return context;      }  } 
复制代码

通过实现 AbstractPluginDeclarer 和 AbstractInterceptor,故障注入插件可以在任何方法中注入想要的故障类型。

接下来我们以在 ClassB 的 sayHello 方法注入自定义异常这个故障为例来看 Sermant 是如何实现故障注入的。

3)      基于 Sermant 热插拔功能实现故障注入插件的安装

首先需要利用 Java Attach API 将 Sermant 加载到已运行的服务中,然后 Sermant 会解析 Java Attach API 传输的参数来执行命令解析,根据解析出来的命令类型来执行对应的命令。当命令类型为 INSTALL-PLUGINS 时,Sermant 会执行安装命令。


Sermant 热插拔功能会先获取故障注入插件包的路径并进行加载,Sermant 采用自定义的类加载器 PluginClassLoader 和 ServiceClassLoader 对插件包中的类进行加载(Sermant 类隔离架构解析可访问文章《Sermant类隔离架构解析——解决JavaAgent场景类冲突的实践》)。然后从 Java Attach API 传输的参数中解析需要增强的类名 ClassB 和方法信息 sayHello,设置配置类 FaultInjectConfig 的属性 className 为 ClassB、methodName 为 sayHello。


​Sermant 加载完成故障注入插件之后,会通过类文件转换器(ClassFileTransformer)对 ClassB 的 sayHello 方法进行字节码增强处理。为了避免对同一个方法进行多次字节码增强带来性能和资源损耗,Sermant 只会对目标方法增强一次,第一次增强时会针对目标方法创建拦截器列表,并将拦截器放入其中,后续增强只需要将拦截器放入该增强方法对应的拦截器列表中即可。如下图所示,图中 InterceptorA 为其他插件对 ClassA 的 print 方法进行增强的拦截器,图中 InterceptorB 为其他插件对 ClassB 的 sayHello 方法进行增强的拦截器。

通过 Sermant 热插拔功能给 ClassB 的 sayHello 方法注入自定义异常的故障之后,在执行 ClassB 的 sayHello 方法时,拦截器就会拦截 sayHello 方法并执行故障注入逻辑,抛出自定义异常。如下图所示:


4)      基于 Sermant 热插拔功能实现故障注入的卸载

当故障注入测试结束后,Sermant 热插拔功能的卸载能力可以将故障注入插件卸载,取消故障注入插件所有的字节码增强,将服务还原到增强前的状态。卸载之后还可以继续其他类型故障的注入测试。

当需要关闭故障注入插件时,可以通过 Java Attach API 来执行 JavaAgent 的动态机制,Sermant 会解析 Java Attach API 传递的命令信息来执行对应的操作,当命令类型为 UNINSTALL_PLUGINS 时 Sermant 会执行卸载流程。


Sermant 热插拔功能会先取消故障注入插件的字节码增强,并清除故障注入插件的插件信息:例如:插件加载时使用的自定义类加载器、加载时创建的 Interceptor、故障注入插件的配置等。


最后 Sermant 会关闭类加载器、清除缓存的插件信息,将故障注入插件完全卸载。

​卸载完成之后不会影响原服务的功能,而且故障注入插件可以再此安装,进行其他的故障测试。

5)          基于 Sermant 热插拔功能实现故障注入插件的第二次安装

故障注入插件卸载完成以后,还可以通过 Sermant 热插拔功能重新安装故障注入插件。故障注入插件支持通过调整 FaultInjectConfig 的属性配置来为其他方法注入故障。重新安装时,可以通过调整 Java Attach API 传递的参数来修改 FaultInjectConfig 的配置,通过配置不同的类名和方法名可以对其他的方法进行故障注入,例如:设置 FaultInjectConfig 中的类名和方法名为 ClassC 和 printResult,就可以在 ClassC 的 printResult 方法中注入故障,重新安装故障注入插件流程和第一次安装没有任何区别,这里就不再赘述。


依赖于 Sermant 热插拔功能的插件卸载能力可以完全卸载故障注入插件,重新安装故障注入插件不需要进行任何特殊处理。重新安装故障注入插件之后,就可以针对新的方法进行故障注入测试。

四、     Sermant 热插拔功能应用探索

通过 Sermant 热插拔功能在故障场景的应用,已经可以看出 Sermant 热插拔功能在故障注入场景下可以发挥巨大的作用,但 Sermant 热插拔功能除了在故障注入场景还可以在故障诊断、以及微服务应用的升级下发挥巨大的作用。例如:

  • 故障诊断:当服务出现故障时,可以通过 Sermant 热插拔功能动态安装插件去获取服务的关键信息,如线程堆栈跟踪、内存使用情况、方法运行时间等。这些信息可以帮助开发人员快速诊断应用程序的故障,并且可以在应用程序运行时进行修改和优化。

  • 微服务治理能力升级:当需要进行微服务治理能力升级时,也可以通过 Sermant 热插拔功能将升级的代码通过插件的形式动态的安装到微服务应用中,而不需要重启服务。

五、     总结

本篇文章介绍了 Sermant 热插拔功能在故障注入场景的应用,通过故障注入场景我们可以发现,Sermant 热插拔功能在故障注入场景下可以发挥重大的作用。利用 Sermant 热插拔功能开发者和使用者可以在微服务运行过程中动态的进行故障注入,还可以多次注入不同的故障,帮助测试微服务的可靠性、稳定性。


Sermant 热插拔功能不仅可用于故障注入,还可用故障诊断、以及微服务应用的升级等场景。Sermant 热插拔功能不在微服务治理方面可以为开发者和使用者提供了更多的便利,帮助他们更有效地管理和维护微服务应用。

 


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



发布于: 刚刚阅读数: 5
用户头像

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

还未添加个人简介

评论

发布
暂无评论
Sermant热插拔能力在故障注入场景的实践_开源_华为云开源_InfoQ写作社区