乐趣区

关于工作流:工作流引擎Activiti的使用进阶详细分析工作流框架中高级功能的使用示例

Activiti 高级性能简介

  • Activit 的高级用例, 会超过 BPMN 2.0 流程的领域, 应用 Activiti 高级性能须要有 Activiti 开发的明确指标和足够的 Activiti 开发教训

    监听流程解析

  • bpmn 2.0 xml文件须要被解析为 Activiti 外部模型, 而后能力在 Activiti 引擎中运行. 解析过程产生在公布流程或在内存中找不到对应流程的时候, 这时会从数据库查问对应的xml
  • 对于每个流程 ,BpmnParser类都会创立一个新的 BpmnParse 实例. 这个实例会作为解析过程中的容器来应用
  • 解析过程:

    • 对于每个 BPMN 2.0 元素, 引擎中都会有一个对应的 org.activiti.engine.parse.BpmnParseHandler 实例
    • 解析器会保留一个 BPMN 2.0 元素与 BpmnParseHandler 实例的映射
    • 默认 Activiti 应用 BpmnParseHandler 来解决所有反对的元素
    • 同时也应用 BpmnParseHandler 来提供执行监听器, 以反对流程历史
  • 能够向 Activiti 引擎中增加自定义的 org.activiti.engine.parse.BpmnParseHandler 实例
  • 常常应用的用例是把执行监听器增加到对应的环节, 来解决一些事件队列.Activiti 在外部就是这样进行历史解决的
  • 要想增加这样的自定义处理器, 须要为 Activit 减少配置:

    <property name="preBpmnParseHandlers">
    <list>
      <bean class="org.activiti.parsing.MyFirstBpmnParseHandler" />
    </list>
    </property>
    
    <property name="postBpmnParseHandlers">
    <list>
      <bean class="org.activiti.parsing.MySecondBpmnParseHandler" />
      <bean class="org.activiti.parsing.MyThirdBpmnParseHandler" />
    </list>
    </property>
  • 当自定义处理器外部逻辑对解决程序有要求时须要思考:

    • 配置到 preBpmnParseHandlers 的 BpmnParseHandler 实例会增加在默认处理器的后面
    • 配置到 postBpmnParseHandlers 的 BpmnParseHandler 实例会增加在默认处理器的前面
  • 接口 - org.activiti.engine.parse.BpmnParseHandler:

    public interface BpmnParseHandler {Collection<Class>? extends BaseElement>> getHandledTypes();
    
    void parse(BpmnParse bpmnParse, BaseElement element);
    
    }
  • BpmnParseHandler 接口中:

    • getHandledTypes()办法会翻译这个解析器解决的所有类型的汇合, 这些都是 BaseElement 的子类, 返回汇合的泛型限度也阐明了这一点
    • 也能够继承 AbstractBpmnParseHandler 类并重写 getHandledType() 办法, 这样就只须要返回一个类型, 而不是一个汇合
    • 这个类也蕴含须要默认解析处理器所须要的办法
    • BpmnParseHandler实例只有在解析器拜访到这个办法返回的类型时才会被调用
  • 示例:

    • BPMN 2.0 xml 蕴含 process 元素时, 就会执行 executeParse 办法中的逻辑
    • 这是一个曾经实现类型转换的办法, 替换 BpmnParseHandler 接口中的 parse 办法

      public class TestBPMNParseHandler extends AbstractBpmnParseHandler<Process> {protected Class<? extends BaseElement> getHandledType() {return Process.class;}
      
      protected void executeParse(BpmnParse bpmnParse, Process element) {..}
      
      }
  • 留神:

    • 在编写自定义解析处理器时, 不要应用任何解析 BPMN 2.0 构造的外部类, 这会导致呈现问题很难定义
    • 应该实现 BpmnParseHandler 接口或集成外部抽象类 org.activiti.engine.impl.bpmn.parser.handler.AbstractBpmnParseHandler
    • 也能够替换默认的 BpmnParseHandler 实例, 把解析 BPMN 2.0 元素 替换为解析Activiti 外部模型:

      <property name="customDefaultBpmnParseHandlers">
      <list>
      ...
      </list>
      </property>
  • 示例: 将所有工作强制设置为异步

    public class CustomUserTaskBpmnParseHandler extends ServiceTaskParseHandler {protected void executeParse(BpmnParse bpmnParse, ServiceTask serviceTask) {
    
      // Do the regular stuff
      super.executeParse(bpmnParse, serviceTask);
    
      // Make always async
      ActivityImpl activity = findActivity(bpmnParse, serviceTask.getId());
      activity.setAsync(true);
    }
    
    }

    反对高并发的 UUID 的 ID 生成器

  • 在高并发的场景中, 默认的 ID 生成器可能因为无奈很快的获取新 ID 区域而导致异样
  • 所有流程引擎都有一个 ID 生成器, 默认的 ID 生成器会在数据库划取一块 ID 范畴, 其余引擎不能应用雷同范畴的 ID
  • 在引擎运行期间, 当默认的 ID 生成器发现曾经越过 ID 范畴时, 就会启动一个新事务来取得新范畴. 在极限的状况下, 高负载会导致问题
  • 对于大部分状况, 默认 ID 生成曾经足够:

    • 默认的 org.activiti.engine.impl.db.DbIdGenerator 有一个 idBlockSize 属性, 能够配置获取 ID 范畴的大小, 这样就能够扭转获取 ID 的行为
    • 另一个能够选用的默认 ID 生成器是org.activiti.engine.impl.persistence.StrongUuidGenerator:

      • 会在本地生成一个惟一的 UUID 作为所有实体的标识
      • 因为生成 UUID 不须要拜访数据库, 所以在高并发环境下的体现比拟好

        • 默认 ID 生成器的性能依赖于运行硬件
  • 将 UUID 生成器配置到 Activiti:

    <property name="idGenerator">
      <bean class="org.activiti.engine.impl.persistence.StrongUuidGenerator" />
    </property>
  • 应用 UUID 生成器须要增加依赖:

     <dependency>
      <groupId>com.fasterxml.uuid</groupId>
      <artifactId>java-uuid-generator</artifactId>
      <version>3.1.3</version>
    </dependency>

    多租户

  • 多租户:

    • 通常是在软件须要作为多个不同组织服务时产生的概念
    • 要害是数据分片, 组织不能看到其余组织的数据
    • 在这种场景下, 组织, 部门, 小组就叫做租户
  • 多租户和装置多个实例是从基本上不同的:

    • 多租户是一个 Activiti 流程引擎实例为每个组织别离运行, 对应不同的数据表
    • 装置多个 Activiti 流程引擎实例时, 尽管 Activiti 是轻量级的, 运行流程引擎不会耗费很多资源, 然而减少了复杂性, 并须要更多保护工作. 然而对于一些场景, 也是正确的解决方案
  • Activiti 的多租户次要围绕着数据分片来实现:

    • Activiti 没有强行校验多租户的规定, 即 Activiti 不会校验查问和应用数据时用户是否应用了正确的租户
    • 校验由 Activiti 引擎的调用者层负责实现
    • Activiti 只确认租户信息会被保留, 并在查问流程数据时会被用到
  • 在向 Activiti 流程引擎公布流程定义时, 须要传递一个租户标识. 是一个字符串, 限度在 256 字符内, 作为租户的惟一标识

     repositoryService.createDeployment()
              .addClassPathResource(...)
              .tenantId("myTenantId")
              .deploy();
  • 通过部署传递租户 Id 有以下作用:

    • 所有蕴含在部署中的流程定义都会继承部署的 tenantId
    • 所有从这些流程定义发动的流程实例, 都会继承流程定义的 tenantId
    • 所有流程实例运行阶段创立的工作都会继承流程实例的 tenantId. 独自运行的 task 也能够蕴含 tenantId
    • 所有流程实例运行阶段创立的分支都会继承流程实例的 tenantId
    • 在流程自身或通过 API 触发一个信号抛出事件能够通过 tenantId 实现. 信号只会在租户环境下执行: 如果有多个信号捕捉事件, 并且名字雷同, 理论只有正确的 tenantId 下的事件会被调用
    • 所有作业 (定时器, 异步调用) 会集成 tenantId, 或者来自流程定义(比方定时开始事件), 或流程实例(运行期创立的作业, 比方异步调用). 这样其实潜在的能够反对为一些租户指定不同优先级的自定义jobExecutor
    • 所有历史实体 (历史流程实例, 工作和节点) 会从对应的运行状态集成 tenantId
    • 作为独自的一部分,model 也能够设置 tenantId. 这里的 model 用来存储 Activiti modeler 设计的 bpmn 2.0 模型
  • 为了确保流程数据应用 tenantId, 所有的查问 API 都能够通过 tenantId 进行查问, 能够应用其余的实体的对应查问实现替换:

    runtimeService.createProcessInstanceQuery()
      .processInstanceTenantId("myTenantId")
      .processDefinitionKey("myProcessDefinitionKey")
      .variableValueEquals("myVar", "someValue")
      .list()

    查问 API 也容许对 tenantId 应用 like 语法,也能够过滤未设置 tenantId 的实体

  • 重要留神点:

    • 因为数据库的限度, 特地是解决 null 的惟一校验. 默认示意未设置租户的 tenantId 的值是 空字符串
    • 流程定义 key, 流程定义 version,tenantId 的组合应该是惟一的, 这个有数据库束缚校验这个规定
    • 要留神 tenantId 不应设置为 null, 会影响一些数据库 Oracle 的查问, 会把空字符串当做 null 解决
    • 这也是为什么 withoutTenantId 查问会查看空字符串或 null. 这意味着雷同的流程定义, 即流程定义 key 雷同能够部署到不同的租户下, 能够领有各自的版本. 当不应用租户时也不会影响应用
  • 这些限度不会影响 Activiti 在集群环境下运行
  • 能够通过调用 repositoryServicechangeDeploymentTenantId(String deploymentId, String newTenantId) 批改tenantId. 会批改之前继承的所有tenantId. 当须要从非多租户环境向多租户环境下切换时, 会十分实用

    执行自定义 SQL

  • Activiti API 容许应用高级 API 操作数据库:

    • 在查问数据方面, 查问 API 和 Native Query API 是十分弱小的
    • 然而对于某些状况, 不够轻便
    • 应用齐全自定义的 SQL 语句:select, insert, update 和 delete. 能够执行在 Activiti 的数据存储之上, 然而齐全又能够配置在流程引擎中: 比方应用事务
  • 为了应用自定义 SQL,Activiti 引擎应用 MyBatis 框架的性能:

    • 因而应用自定义 SQL 的第一件事, 要创立 MyBatis 映射类
    • 假如不须要全副的工作数据, 只须要其中的一小部分. 能够应用 Mapper 实现:

      public interface MyTestMapper {@Select("SELECT ID_ as id, NAME_ as name, CREATE_TIME_ as createTime FROM ACT_RU_TASK")
      List<Map<String, Object>> selectTasks();}
  • Mapper 须要设置到流程引擎配置中:

    ...
    <property name="customMybatisMappers">
    <set>
      <value>org.activiti.standalone.cfg.MyTestMapper</value>
    </set>
    </property>
    ...
  • 这个 Mapper 是一个接口:

    • MyBatis 框架会在运行阶段为这个接口创立一个实例
    • 返回值是没有类型的, 是一个 map 的 list, 和对应的行列对应
    • 如果须要也能够应用 MyBatis 映射
  • 执行下面的查问:

    • 能够应用 managementService.executeCustomSql 办法
    • 这个办法须要一个 CustomSqlExecution 实体
    • 这个实体类是一个封装类, 暗藏了引擎的外部实现所需执行的信息
    • 然而因为 Java 泛型, 查问返回的后果可读性差
  • 示例:

    • mapper 类和返回类型类
    • 简略调用 mapper 办法 并返回后果

      CustomSqlExecution<MyTestMapper, List<Map<String, Object>>> customSqlExecution =
            new AbstractCustomSqlExecution<MyTestMapper, List<Map<String, Object>>>(MyTestMapper.class) {public List<Map<String, Object>> execute(MyTestMapper customMapper) {return customMapper.selectTasks();
      }
      
      };
      
      List<Map<String, Object>> results = managementService.executeCustomSql(customSqlExecution);

      list 中的 Map 只蕴含id,name 和 create time, 不是全副的工作对象

  • 能够通过这样的形式执行任意 SQL:

     @Select({
          "SELECT task.ID_ as taskId, variable.LONG_ as variableValue FROM ACT_RU_VARIABLE variable",
          "inner join ACT_RU_TASK task on variable.TASK_ID_ = task.ID_",
          "where variable.NAME_ = #{variableName}"
      })
      List<Map<String, Object>> selectTaskWithSpecificVariable(String variableName);

    应用这种办法, 工作表会与变量表关联. 只会取得对应名称的变量, 工作 Id 和对应的数值会被返回

    应用 ProcessEngineConfigurator 实现流程引擎配置

  • 能够应用 ProcessEngineConfigurator 实现一种高级的扩大流程引擎的配置:

    • 创立一个 org.activiti.engine.cfg.ProcessEngineConfigurator 接口的实现
    • 注入到流程引擎配置里

      <bean id="processEngineConfiguration" class="...SomeProcessEngineConfigurationClass">
      
      ...
      
      <property name="configurators">
          <list>
              <bean class="com.mycompany.MyConfigurator">
                  ...
              </bean>
          </list>
      </property>
      
      ...
      
      </bean>
  • 实现 ProcessEngineConfigurator 接口须要实现两个办法:

    • configure: 将 ProcessEngineConfiguration 作为参数, 能够通过这种办法增加自定义配置, 这个办法在流程创立之前, 在所有默认配置执行之前保障调用到
    • getPriority: 如果一些 configurator 存在依赖项的时候, 容许对 configurator 进行排序
  • configurator 实例:

    • LDAP 集成:

      • 这个 configurator 用来替换默认的 usergroup管理器类, 应用解决 LDAP 用户存储的类
      • 基本上一个 configurator 容许很大水平上批改或加强流程引擎, 对高级的场景十分有用
    • 应用自定义的版本替换流程定义缓存, 如下:

      public class ProcessDefinitionCacheConfigurator extends AbstractProcessEngineConfigurator {public void configure(ProcessEngineConfigurationImpl processEngineConfiguration) {MyCache myCache = new MyCache();
              processEngineConfiguration.setProcessDefinitionCache(enterpriseProcessDefinitionCache);
      }
      
      }
  • 流程引擎配置器也能够通过 ServiceLoader 主动从 classpath 中加载:

    • 放在 jar 中的 configurator 实现必须放在 classpath
    • 并在 jarMETA-INF/services目录下蕴含一个 org.activiti.engine.cfg.ProcessEngineConfigurator 文件
    • 文件的内容是自定义实现的全类名
    • 当流程引擎启动时, 日志会显示找到了哪些configurator

      INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - Found 1 auto-discoverable Process Engine Configurators
      INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - Found 1 Process Engine Configurators in total:
      INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - class org.activiti.MyCustomConfigurator
  • 这种 ServiceLoader 的形式在某些环境下可能无奈失常运行. 应用 ProcessEngineConfigurationenableConfiguratorServiceLoader属性来禁用这个性能, 这个属性的默认值为 true

    启动平安 BPMN 2.0xml

  • 大多数状况下,BPMN 2.0 流程公布到 Activiti 引擎是在严格的管制下的
  • 然而在某些状况下, 可能须要把比拟随便的 BPMN 2.0 xml 上传到引擎, 这时就要要思考歹意用户会攻打服务器
  • 为了防止 BPMN 2.0xml 引擎服务器受到攻打, 能够在引擎中设置enableSafeBpmnXml:

    <property name="enableSafeBpmnXml" value="true"/>
  • 默认这个性能没有开启. 因为这个性能须要应用 StaxSource
  • 因为 JDK6,JBoss 应用的是旧版的 xml 解析实现, 无奈应用 StaxSource 类, 所以不能启用平安的 BPMN 2.0xml
  • 如果 Activiti 运行的平台反对平安的 BPMN 2.0xml 性能, 倡议关上

    事件日志

  • 在 Activiti 5.16 版本中, 增加了事件日志机制:

    • 这种日志机制构建在通用目标下的 Activiti 引擎的事件机制, 默认是禁用的
    • 目标是由引擎产生的事件会被捕捉, 蕴含所有事件数据的 map 会被创立进去, 并提供给org.activiti.engine.impl.event.logger.EventFlusher, 会把数据刷新到别的中央
    • 默认会应用一个简略地基于数据库的事件处理器或者叫作刷新器, 会应用 jacksonmap转换为 JSON, 并保留到数据库中的EventLogEntryEntity 实体
    • 默认会创立数据库日志表ACT_EVT_LOG. 如果没有应用事件日志, 能够删除这个表
  • 启用数据库日志:

    processEngineConfiguration.setEnableDatabaseEventLogging(true);

    或者在流程引擎运行阶段:

    databaseEventLogger = new EventLogger(processEngineConfiguration.getClock());
    runtimeService.addEventListener(databaseEventLogger);
  • EventLogger 类能够继承:

    • 在须要应用自定义的数据日志时:

      • createEventFlusher() 办法须要返回一个 org.activiti.engine.impl.event.logger.EventFlusher 接口的实例
      • managementService.getEventLogEntries(startLogNr, size) 能够获取 Actviti 的 EventLogEntryEntity 实例
  • 能够应用大数据的 NoSQL 存储: MongoDb,Elastic Search 等等来存储 JSON。
  • 应用的类是可插拔的: org.activiti.engine.impl.event.logger.EventLogger/EventFlusher和很多 EventHandler
  • 能够切换成自定义利用场景: 不在数据库中存储 JSON, 而是放到队列或大数据存储中
  • 留神:

    • 事件日志机制是 Activiti 传统历史管理器的附加品
    • 尽管所有数据都在数据库表中, 然而并没有为查问优化, 不容易获取
    • 实在的应用场景:

      • 审计跟踪
      • 将事件日志数据放到大数据存储中
退出移动版