写点什么

工作流 Activiti 框架中仿真引擎 Crystalball 的使用分析

发布于: 2021 年 06 月 14 日
工作流Activiti框架中仿真引擎Crystalball的使用分析

Activiti-Crystalball 简介

  • Activiti-Crystalball(CrystalBall)是 Activiti 业务流程管理平台的仿真引擎 .CrystalBall 可以使用用用户模拟流程场景:

  • 决策支持: 对于生产流程, 比如是否应该向系统添加更多资料以达到截止日期

  • 优化和验证: 测试修改并验证影响

  • 培训: 模拟器可以用来在使用前培训员工

  • CrystalBall 是独立的:

  • 不需要创建单独的模拟模型和引擎

  • 不需要为模拟创建不同的报告

  • 不需要为模拟引擎准备很多数据

  • CrystalBall 模拟器是基于 Activiti 的:

  • 容易复制数据

  • 启动模拟器

  • 从历史中重播流程行为

CrystalBall 内部

  • CrystalBall 是一个离散事件模拟器

  • CrystalBall 的一个实现是 org.activiti.crystalball.simulator.SimpleSimulationRun:


init();
SimulationEvent event = removeSimulationEvent();
while (!simulationEnd(event)) { executeEvent(event); event = removeSimulationEvent(); }
close();
复制代码


  • SimulationRun 可以执行由不同源生成的模拟事件

历史分析

  • 模拟器可以使用的用例之一是分析历史

  • 生产环境没有提供任何重复和调试 bug 的机会,这就是为什么基本不可能把流程引擎恢复到生产环境出现问题时完全一样的状态.有以下原因:

  • 时间: 流程实例可能执行好几个月

  • 并发: 流程实例会和别的实例一起运行,问题可能只产生于并发执行的情况

  • 用户: 很多用户可以参与到流程实例中,流程实例会影响到出现问题的状态

  • 模拟器可以更好的暴露以上的问题:

  • 模拟过程是虚拟的,不会依赖真实环境

  • Activiti 流程引擎本身是虚拟的,不需要创建虚拟流程引擎,作为模拟环境使用

  • 并发场景也是原生的

  • 用户行为都会记录日志,并可以从日志重现,根据需要进行预测和生成

  • 分析历史的最好办法是重现一次,真实环境很难实现重现,但是模拟器就可以实现重现

历史的事件

  • 重现历史最重要的事情是记录影响状态的事件

  • 流程是由用户事件驱动的,可以使用两种事件源:

  • 流程实例: 只支持原始的 Activiti-Crystalball 项目

  • ActivitiEvent 日志: 可以向引擎添加想要记录日志的 ActivitiEventListener. 事件日志可以保存下来,用于后续的分析

  • ActivitiEventListener 的一个基本实现: org.activiti.crystalball.simulator.delegate.event.impl.InMemoryRecordActivitiEventListener


 @Override  public void onEvent(ActivitiEvent event) {    Collection<SimulationEvent> simulationEvents = transform(event);    store(simulationEvents);  }
复制代码


  • 事件会被保存,可以对历史进行重现

回放

  • 回放的好处是可以一遍一遍播放,直到完全理解发生了什么

  • Crystalball 模拟器是基于真实数据,真实用户行为




  • 示例: 理解回放工作的最好方法是一步一步解释

  • 基于 JUnit 的测试例子 :org.activiti.crystalball.simulator.delegate.event.PlaybackRunTest


<process id="theSimplestProcess" name="Without task Process">    <documentation>This is a process for testing purposes</documentation>
<startEvent id="theStart"/> <sequenceFlow id="flow1" sourceRef="theStart" targetRef="theEnd"/> <endEvent id="theEnd"/>
</process>
复制代码


流程发布,可以用于真实和模拟的运行:


  • 记录事件


// get process engine with record listener to log events  ProcessEngine processEngine = (new RecordableProcessEngineFactory(THE_SIMPLEST_PROCESS, listener))  .getObject();
// start process instance with variables Map<String,Object> variables = new HashMap<String, Object>(); variables.put(TEST_VARIABLE, TEST_VALUE); processEngine.getRuntimeService().startProcessInstanceByKey(SIMPLEST_PROCESS, BUSINESS_KEY,variables);
// check process engine status - there should be one process instance in the history checkStatus(processEngine);
// close and destroy process engine EventRecorderTestUtils.closeProcessEngine(processEngine, listener); ProcessEngines.destroy();
复制代码


startProcessInstanceByKey 方法调用后,记录 ActivitiEventType.ENTITY_CREATED


  • 开始模拟运行:


final SimpleSimulationRun.Builder builder = new SimpleSimulationRun.Builder();  // init simulation run  // get process engine factory - the only difference from RecordableProcessEngineFactory that log listener is not added  DefaultSimulationProcessEngineFactory simulationProcessEngineFactory = new DefaultSimulationProcessEngineFactory(THE_SIMPLEST_PROCESS);  // configure simulation run  builder.processEngine(simulationProcessEngineFactory)         // set playback event calendar from recorded events         .eventCalendar(new PlaybackEventCalendarFactory(new SimulationEventComparator(), listener.getSimulationEvents()))         // set handlers for simulation events         .customEventHandlerMap(EventRecorderTestUtils.getHandlers());  SimpleSimulationRun simRun = builder.build();
simRun.execute(new NoExecutionVariableScope());
// check the status - the same method which was used in record events method checkStatus(simulationProcessEngineFactory.getObject());
// close and destroy process engine simRun.getProcessEngine().close(); ProcessEngines.destroy();
复制代码




  • 其它示例在 org.activiti.crystalball.simulator.delegate.event.PlaybackProcessStartTest 中

调试流程引擎

  • 回放限制执行所有模拟事件一次性

  • 调试器允许将流程事件自行拆分成更小的步骤,在步骤之间观察流程引擎的状态

  • SimpleSimulationRun 实现了 SimulationDebugger 接口 .SimulationDebugger 可以一步一步执行模拟事件,可以模拟特定时间的执行:


 /**  * Allows to run simulation in debug mode  */  public interface SimulationDebugger {  /**  * initialize simulation run  * @param execution - variable scope to transfer variables from and to simulation run  */  void init(VariableScope execution);
/** * step one simulation event forward */ void step();
/** * continue in the simulation run */ void runContinue();
/** * execute simulation run till simulationTime */ void runTo(long simulationTime);
/** * execute simulation run till simulation event of the specific type */ void runTo(String simulationEventType);
/** * close simulation run */ void close(); }
复制代码


  • 执行 SimpleSimulationRunTest 来观察流程引擎调试器的运行

重播

  • 回放需要创建另一个流程引擎实例,模拟环境配置

  • 重播工作在真实的流程引擎之上,重播在运行的流程引擎中执行模拟事件:

  • 结论是重播是实时运行的,实时意味着会被立即执行**




重播一个流程实例示例: ReplyRunTest


  • 第一部分 :初始化流程引擎,启动一个流程实例,完成流程实例的任务


ProcessEngine processEngine = initProcessEngine();
TaskService taskService = processEngine.getTaskService(); RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<String, Object>(); variables.put(TEST_VARIABLE, TEST_VALUE); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(USERTASK_PROCESS, BUSINESS_KEY, variables);
Task task = taskService.createTaskQuery().taskDefinitionKey("userTask").singleResult(); TimeUnit.MILLISECONDS.sleep(50); taskService.complete(task.getId());
复制代码


使用的流程引擎是基础的 InMemoryStandaloneProcessEngine: 配置了 InMemoryRecordActivitiEventListener(记录 Activiti 事件,并转换为模拟事件)和 UserTaskExecutionListener(当创建新用户任务时,新任务会重播流程实例,把任务完成事件放到事件日历中)


  • 第二部分 :在原始流程相同的引擎引擎上启动模拟调试器

  • 重播事件处理器使用 StartReplayProcessEventHandler 替换 StartProcessEventHandler

  • StartReplayProcessEventHandler 获取流程实例 Id 来重播,在流程实例启动的初始位置处理

  • StartProcessEventHandler 在开始阶段,会创建一个新流程实例,包含一个变量.变量名为 _replay.processInstanceId. 变量用来保存重播的流程实例 Id

  • SimpleSimulationRun 不同 ,ReplaySimulationRun:

  • 不会创建和关闭流程引擎实例

  • 不会修改模拟时间


final SimulationDebugger simRun = new ReplaySimulationRun(processEngine,getReplayHandlers(processInstance.getId()));
复制代码


  • 开始重播流程实例:

  • 一开始, 没有运行的流程实例

  • 只有一个已完成的,在历史中的流程实例

  • 在初始化后,会在事件日历中添加一个模拟事件-用来启动流程实例,重播已经完成的流程实例


 simRun.init();
// original process is finished - there should not be any running process instance/task assertEquals(0, runtimeService.createProcessInstanceQuery().processDefinitionKey(USERTASK_PROCESS).count()); assertEquals(0, taskService.createTaskQuery().taskDefinitionKey("userTask").count());
simRun.step();
// replay process was started assertEquals(1, runtimeService.createProcessInstanceQuery().processDefinitionKey(USERTASK_PROCESS).count()); // there should be one task assertEquals(1, taskService.createTaskQuery().taskDefinitionKey("userTask").count());
复制代码


  • 任务创建时,UserTaskExecutionListener 会创建一个新模拟事件来结束用户任务:


  simRun.step();
// userTask was completed - replay process was finished assertEquals(0, runtimeService.createProcessInstanceQuery().processDefinitionKey(USERTASK_PROCESS).count()); assertEquals(0, taskService.createTaskQuery().taskDefinitionKey("userTask").count());
复制代码


  • 模拟结束.这时可以继续启动另一个流程实例或者事件,然后关闭 simRun 和流程引擎:


  simRun.close();  processEngine.close();  ProcessEngines.destroy();
复制代码


发布于: 2021 年 06 月 14 日阅读数: 13
用户头像

一位攻城狮的自我修养 2021.04.06 加入

分享技术干货,面试题和攻城狮故事。 你的关注支持是我持续进步的最大动力! https://github.com/ChovaVea

评论

发布
暂无评论
工作流Activiti框架中仿真引擎Crystalball的使用分析