由于最近在基于chaosblade开发公司自定义的插件,我将 chaosblade-exec-jvm 加载流程以及执行机制梳理了一遍,本篇文章也基于此,作为源码阅读笔记
chaosblade 在执行 Java 应用故障注入时,分为两部分,首先通过prepare
命令加载插件,以监听故障注入命令;其次是create
命令创建具体的故障注入命令。
框架入口位置是在com.alibaba.chaosblade.exec.bootstrap.jvmsandbox.SandboxModule
,在调用 prepare 命令时,首先会调用onLoad()
方法,会做三件事
create
,status
,destroy
三个 handler,用于处理创建故障注入,查询故障注入状态,销毁故障注入 3 个具体的命令如果返回成功后,sleep 5s,等待初始化完成
然后再通过chaosblade
调用active()
方法,而chaosblade-exec-jvm
通过接受到请求后,调用onActive()
方法,用于加载所有的 plugin,具体逻辑如下:
plugins
目录下所有的 plugin,然后通过 spi 加载所有的 plugin如果上述两步都执行成功,就返回prepare
执行成功
从注册的 handler 中获取到 createHandler
判断是否加载了插件以及一些参数校验
根据target
,actionFlag
,params
初始化 model 对象,根据 model 对象执行对应的 predict 方法,即可以在 plugin 中实现predicate
方法,可以执行一些自定义的action
参数逻辑校验,如果成功,则继续,反之,返回参数校验失败。例如,在delay
故障的时候,需要判断--time
为必填项
public PredicateResult predicate(ActionModel actionModel) {
if (StringUtil.isBlank(actionModel.getFlag(timeFlag.getName()))) {
return PredicateResult.fail("less time argument");
}
return PredicateResult.success();
}
preMatcherPredict()
,可以做一些自定义的matcher
参数逻辑校验,或者做一些参数变形。比如在我们自定义的逻辑中,我们需要根据我们的应用唯一标识,获取到该应用下所有可用的 ip 列表,根据如下逻辑进行参数变形protected PredicateResult preMatcherPredicate(Model model) {
if (model == null) {
return PredicateResult.fail("matcher not found for dsf");
}
MatcherModel matcher = model.getMatcher();
Set<String> keySet = matcher.getMatchers().keySet();
if (keySet.contains(DsfConstant.CONSUMER_KEY)) {
if (keySet.contains(DsfConstant.SERVICE_GROUP)) {
return PredicateResult.success();
} else {
return PredicateResult.fail("less sgName option of consumer for dsf experiment");
}
}
if (keySet.contains(DsfConstant.PROVIDER_KEY)) {
if (keySet.contains(DsfConstant.SERVICE_GROUP) &&
keySet.contains(DsfConstant.ENV_TYPE)) {
if (!keySet.contains(DsfConstant.IP_LIST)) {
String env = (String) matcher.getMatchers().get(DsfConstant.ENV_TYPE);
String sgName = (String) matcher.getMatchers().get(DsfConstant.SERVICE_GROUP);
String kernelUrl = getKernelUrl(env, sgName);
String endpoints = null;
if (kernelUrl != null) {
try {
endpoints = getEndpointInSgGroup(kernelUrl);
} catch (IOException ignored) {
}
}
if (endpoints != null) {
matcher.remove(DsfConstant.SERVICE_GROUP);
matcher.remove(DsfConstant.ENV_TYPE);
matcher.add(DsfConstant.IP_LIST, endpoints);
LOGGER.info("add ip list {} for dsf", endpoints);
}
} else {
matcher.remove(DsfConstant.SERVICE_GROUP);
matcher.remove(DsfConstant.ENV_TYPE);
}
}
return PredicateResult.success();
}
return PredicateResult.fail("less necessary matcher is consumer or provider for dsf");
}
执行 matcher 的predict
参数校验逻辑,可以对每个参数进行具体的逻辑校验
将实验 id 注册进去,如果 id 已存在,则返回实验 id 存在的报错
懒加载 plugin,根据 target 获取到 pluginBeans,先判断 pluginBeans 是否已经加载 (即标志位是否为 true,其实 plugin 已经通过 spi 加载过了),如果没有加载则通过对应 target 下的 plugin 标志位设置为 true,并加入到 listener 中,加入的具体逻辑就是开始 watch pointcut 中定义的 class 和 method。当然可以通过设置 matcher 来控制是 watch before 还是 after 事件
执行具体的preCreate
逻辑,如果 plugin 的 modelSpec 实现了PreCreateInjectionModelHandler
的preCreate
方法,则会执行具体的 preCreate 逻辑,即可以通过该方法,进行一些直接的故障注入逻辑,比如:我的需求如果是调用 create 命令时,需要将内存占满,则可以通过实现 preCreate 方法,来实现内存占用逻辑,因为不需要进行时间监听,则不需要实现对应的 pointcut 监听
如果全部操作成功就返回成功,反正,返回 server error 的报错
而 watch 事件是怎么通知到插件逻辑的呢?具体逻辑是在com.alibaba.chaosblade.exec.bootstrap.jvmsandbox.BeforeEventListener
和com.alibaba.chaosblade.exec.bootstrap.jvmsandbox.AfterEventListener
的onEvent()
中,我们以 beforeEvent 为示例,具体步骤为:
getPointCut()
不为空,或者切面的类不是以java
开头,或者sun
开头,或者[
开头,或者com.alibaba.chaosblade
开头,并且匹配到了classMatcher.isMatched()
,则会将 class 注册到监听类中,用户可以定义不同的 class match 逻辑来进行筛选,具体的匹配逻辑位于com.alibaba.chaosblade.exec.common.aop.matcher.clazz
方法匹配则是通过methodMatcher.isMatched()
方法来判断是否匹配到了需要监听的方法,用户可以通过定义不同的 method match 逻辑来进行筛选,具体的逻辑位于com.alibaba.chaosblade.exec.common.aop.matcher.method
如果 event 是对应的事件,比如是 beforeEvent,通过 classloader 获取 class,通过反射获取对应的方法,这里获取到的 class 和 method 就是通过 pointcut 配置的监听切面
获取到该 plugin 的 enhancer 实现类,做对应的切面增强逻辑。首先看这个 target 实验是否存在,如果存在直接返回;如果不存在,则调用业务方实现类的doBeforeAdvice
逻辑,如果需要做具体的切面增强,需要返回一个EnhancerModel
对象,这个对象是业务方从切面获取到的参数配置,即需要对哪些参数做切面增强,如果该对象为 null,那直接返回,不进行增强;如果不是 null,那么从内存中根据 target 查询 create 的参数配置,如果参数不匹配切面的参数,说明不匹配,反正才进行增强;然后会判断是否配置了effect-count
,即需要控制故障注入增强的次数,或者effect-percent
,故障注入的影响百分比;如果没有触发上述的限流,则根据 actionExecutor 进行具体的故障注入。
那么怎么实现 executor 逻辑呢?chaosblade 提供了一些默认的 executor 实现,比如,如果 modelSpec 继承FrameworkModelSpec
,则默认实现了延迟和抛异常两种故障,则只需在doBeforeAdvice
实现具体的延迟和抛异常切面。如果默认的 executor 满足不了需求,可以实现ActionExecutor
的 run 方法,实现自定义的故障注入
整体来看,destroy 流程还是比较简单的,整体分为 3 个部分
PreDestroyInjectionModelHandler
实验类的preDestroy
方法,可以自定义一些销毁前钩子函数整体来看,chaosblade-exec-jvm
可扩展性还是非常高的,可以非常方便实现自定义的 plugin 逻辑。不过问题是,当前的插件过多,会导致业务逻辑过于分散了;好处是扩展插件时会比较方便,并且不会担心项目后续迭代造成与当前插件的代码冲突