工作流框架中的仿真引擎的使用分析!实现用户流程场景的模拟仿真

工作流框架

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();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容