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在集群环境下运行
能够通过调用repositoryService的changeDeploymentTenantId(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用来替换默认的user和group管理器类,应用解决LDAP用户存储的类
- 基本上一个configurator容许很大水平上批改或加强流程引擎,对高级的场景十分有用
应用自定义的版本替换流程定义缓存, 如下:
public class ProcessDefinitionCacheConfigurator extends AbstractProcessEngineConfigurator {public void configure(ProcessEngineConfigurationImpl processEngineConfiguration) { MyCache myCache = new MyCache(); processEngineConfiguration.setProcessDefinitionCache(enterpriseProcessDefinitionCache);}}
流程引擎配置器也能够通过ServiceLoader主动从classpath中加载:
- 放在jar中的configurator实现必须放在classpath下
- 并在jar的META-INF/services目录下蕴含一个org.activiti.engine.cfg.ProcessEngineConfigurator文件
- 文件的内容是自定义实现的全类名
当流程引擎启动时,日志会显示找到了哪些configurator
INFO org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl - Found 1 auto-discoverable Process Engine ConfiguratorsINFO 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的形式在某些环境下可能无奈失常运行.应用ProcessEngineConfiguration的enableConfiguratorServiceLoader属性来禁用这个性能,这个属性的默认值为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, 会把数据刷新到别的中央
- 默认会应用一个简略地基于数据库的事件处理器或者叫作刷新器,会应用jackson把map转换为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传统历史管理器的附加品
- 尽管所有数据都在数据库表中,然而并没有为查问优化,不容易获取
实在的应用场景:
- 审计跟踪
- 将事件日志数据放到大数据存储中