小秦哥 发表于 2024-5-16 15:10:55

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

本文分享自华为云社区《Sermant热插拔能力在故障注入场景的实践》,作者:张豪鹏 华为云高级软件工程师
一、 媒介

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

1) 什么是故障注入?

故障注入是一种测试方法,它通过在系统中故意引入错误或故障,来测试系统对这些错误或故障的响应和规复能力,并验证系统是否能够正常处置惩罚这些异常环境。下图是故障注入测试中的一些常见故障:
https://bbs-img.huaweicloud.com/blogs/img/20240403/1712132173695726415.png
通过故障注入技术,测试人员可以在Java应用中模拟各种故障场景,以便评估应用的响应能力和规复能力,还可以帮助提前拦截和发现Java应用埋伏的可靠性问题,提升应用稳固性,避免现网出现重大质量变乱。例如:

[*]通过在指定方法中抛出自界说异常可以测试系统在异常环境下的稳固性
[*]通过修改指定方法的返回值可以测试系统在异常数据环境下的处置惩罚能力
[*]通过数据库故障可以测试系统在异常环境下是否可以保持数据一致性等。
2) Java应用实现故障注入方案的难点是什么?

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

[*]通过修改代码来注入故障时,每次注入新的故障都需要进行代码修改并重启服务,影响故障注入的服从
[*]通过开关配置来进行故障注入时,需要增加大量的开关逻辑判定
因此怎样在不重启应用的环境下实现故障注入,并且可以重复的进行故障注入对提高故障注入测试的服从和全面性变得至关紧张。
三、 Sermant热插拔功能在故障注入场景下的应用

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

Sermant热插拔功能是基于JavaAgent动态加载机制实现的,可以在服务不停机状态下进行Java Agent和插件的安装、卸载,而且安装、卸载插件时不会影响其他插件的正常运行。
下图为Sermant热插拔能力的示意图,Sermant可以在服务运行过程中进行Agent动态安装,Agent安装完成后可以通过动态安装、卸载插件来调整所需的微服务管理能力,也可以卸载整个Agent。
https://bbs-img.huaweicloud.com/blogs/img/20240403/1712132244337928314.png
2) 基于Sermant实现故障注入插件

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

[*]通过实现PluginConfig来界说插件需要的配置。
[*]通过实现AbstractPluginDeclarer来声明进行字节码加强的方法,类似于面向切面编程中的Joint point。
[*]通过实现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。
https://bbs-img.huaweicloud.com/blogs/img/20240403/1712133098973390382.png
Sermant加载完成故障注入插件之后,会通过类文件转换器(ClassFileTransformer)对ClassB的sayHello方法进行字节码加强处置惩罚。为了避免对同一个方法进行多次字节码加强带来性能和资源损耗,Sermant只会对目标方法加强一次,第一次加强时会针对目标方法创建拦截器列表,并将拦截器放入其中,后续加强只需要将拦截器放入该加强方法对应的拦截器列表中即可。如下图所示,图中InterceptorA为其他插件对ClassA的print方法进行加强的拦截器,图中InterceptorB为其他插件对ClassB的sayHello方法进行加强的拦截器。
https://bbs-img.huaweicloud.com/blogs/img/20240403/1712133228862760230.png
通过Sermant热插拔功能给ClassB的sayHello方法注入自界说异常的故障之后,在实行ClassB的sayHello方法时,拦截器就会拦截sayHello方法并实行故障注入逻辑,抛出自界说异常。如下图所示:
https://bbs-img.huaweicloud.com/blogs/img/20240403/1712133237177712952.png
4) 基于Sermant热插拔功能实现故障注入的卸载

当故障注入测试竣事后,Sermant热插拔功能的卸载能力可以将故障注入插件卸载,取消故障注入插件所有的字节码加强,将服务还原到加强前的状态。卸载之后还可以继承其他类型故障的注入测试。
当需要关闭故障注入插件时,可以通过Java Attach API来实行JavaAgent的动态机制,Sermant会剖析Java Attach API传递的命令信息来实行对应的操作,当命令类型为UNINSTALL_PLUGINS时Sermant会实行卸载流程。
Sermant热插拔功能会先取消故障注入插件的字节码加强,并扫除故障注入插件的插件信息:例如:插件加载时使用的自界说类加载器、加载时创建的Interceptor、故障注入插件的配置等。
https://bbs-img.huaweicloud.com/blogs/img/20240403/1712133629788383846.png
最后Sermant会关闭类加载器、扫除缓存的插件信息,将故障注入插件完全卸载。
https://bbs-img.huaweicloud.com/blogs/img/20240403/1712133661037116231.png
卸载完成之后不会影响原服务的功能,而且故障注入插件可以再此安装,进行其他的故障测试。
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 作为专注于服务管理领域的字节码加强框架,致力于提供高性能、可扩展、易接入、功能丰富的服务管理体验,并会在每个版本中做好性能、功能、体验的关照,广泛欢迎大家的参加。

[*]Sermant官网:https://sermant.io
[*]GitHub仓库地址:https://github.com/huaweicloud/Sermant
点击关注,第一时间了解华为云新鲜技术~
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Sermant热插拔能力在故障注入场景的实践