由于最近在基于chaosblade开发公司自定义的插件,我将 chaosblade-exec-jvm 加载流程以及执行机制梳理了一遍,本篇文章也基于此,作为源码阅读笔记

加载流程

chaosblade 在执行 Java 应用故障注入时,分为两部分,首先通过prepare命令加载插件,以监听故障注入命令;其次是create命令创建具体的故障注入命令。

框架入口位置是在com.alibaba.chaosblade.exec.bootstrap.jvmsandbox.SandboxModule,在调用 prepare 命令时,首先会调用onLoad()方法,会做三件事

  1. 将自己设置为 plugin 生命周期的监听器,用于监听管理 plugin 的生命周期
  2. 注册createstatusdestroy三个 handler,用于处理创建故障注入,查询故障注入状态,销毁故障注入 3 个具体的命令
  3. 设置当前标志位,表示当前已经是加载了插件

如果返回成功后,sleep 5s,等待初始化完成

然后再通过chaosblade调用active()方法,而chaosblade-exec-jvm通过接受到请求后,调用onActive()方法,用于加载所有的 plugin,具体逻辑如下:

如果上述两步都执行成功,就返回prepare执行成功

执行流程

create 流程

从注册的 handler 中获取到 createHandler

public PredicateResult predicate(ActionModel actionModel) {
        if (StringUtil.isBlank(actionModel.getFlag(timeFlag.getName()))) {
            return PredicateResult.fail("less time argument");
        }
        return PredicateResult.success();
    }
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");
    }

而 watch 事件是怎么通知到插件逻辑的呢?具体逻辑是在com.alibaba.chaosblade.exec.bootstrap.jvmsandbox.BeforeEventListenercom.alibaba.chaosblade.exec.bootstrap.jvmsandbox.AfterEventListeneronEvent()中,我们以 beforeEvent 为示例,具体步骤为:

那么怎么实现 executor 逻辑呢?chaosblade 提供了一些默认的 executor 实现,比如,如果 modelSpec 继承FrameworkModelSpec,则默认实现了延迟和抛异常两种故障,则只需在doBeforeAdvice实现具体的延迟和抛异常切面。如果默认的 executor 满足不了需求,可以实现ActionExecutor的 run 方法,实现自定义的故障注入

destroy 流程

整体来看,destroy 流程还是比较简单的,整体分为 3 个部分

  1. 从实验列表中移除掉当前实验
  2. 从监控列表中移除掉当前实验
  3. 执行PreDestroyInjectionModelHandler实验类的preDestroy方法,可以自定义一些销毁前钩子函数

整体来看,chaosblade-exec-jvm可扩展性还是非常高的,可以非常方便实现自定义的 plugin 逻辑。不过问题是,当前的插件过多,会导致业务逻辑过于分散了;好处是扩展插件时会比较方便,并且不会担心项目后续迭代造成与当前插件的代码冲突


↙↙↙阅读原文可查看相关链接,并与作者交流