camunda7流程引擎支持两种执行服务任务的方式:
- 内部任务:同步调用与流程应用程序一起部署的代码
- 外部任务:在列表中提供可由员工轮询的工作单元
第一个选项可以通过委托代码(Delegation) 或者脚本(Script)实现.相反,外部(服务)任务的工作方式是Process Engine流程引擎将工作单元发布给Worker来获取和完成。我们将其称为外部任务模式(the external task pattern)。外部任务模式是camunda工作流引擎特有的机制,其它开源流程引擎activiti、flowable、jbpm等没有外部任务功能。
请注意,上述区别并未说明实际的“业务逻辑”是在本地实现还是作为远程服务实现。由内部服务任务调用的Java委托可以实现业务逻辑本身,也可以调用Web/REST服务,向另一个系统发送消息等等。外部工作人员也是如此。工作人员可以直接实现业务逻辑,也可以再次委托给远程系统。
设计流程时,外部任务可以在流程设计器上配置出来:
如何配置一个external task外部任务和示例demo,详细文档:https://lowcode.blog.csdn.net/article/details/136164616
在线体验camunda流程引擎功能:http://www.yunchengxc.com
1、外部任务模式
执行外部任务(external task)的流程在概念上可以分为三个步骤,如下图所示:
- 流程引擎(Process Engine):创建外部任务实例
- 外部工作者(External Worker):获取并锁定外部任务
- 外部工作者(External Worker) & 流程引擎(Process Engine):完成外部任务实例
上面的这个例子描述的是让客户确认收货地址是否正确,客户确认收货地址(Validate Address)这个动作就是一个外部任务。当Process Engine遇到配置为外部处理的服务任务时,它会创建一个外部任务实例并将其添加到外部任务列表中(步骤1)。任务实例接收标识要执行的工作的性质的主题。在将来的某个时间,外部工作人员可能会获取并锁定特定主题集的任务(步骤2)。为了防止多个工作线程同时获取一个任务,任务具有基于时间戳的锁定,该锁定在获取任务时设置。只有当锁过期时,另一个工作线程才能再次获取该任务。当外部工作人员完成了所需的工作时,它可以向流程引擎发出信号,以在服务任务之后继续流程执行(步骤3)。
1.1、如何理解外部任务-类比用户任务
外部任务在概念上与用户任务非常相似。在第一次尝试理解外部任务模式时,将其与用户任务进行类比会很有帮助:用户任务由流程引擎创建并添加到任务列表中。然后,流程引擎等待人工用户查询列表,获取任务,然后完成任务。外部任务与此类似:创建外部任务,然后将其添加到主题中。然后,外部应用程序查询主题并锁定任务。锁定任务后,应用程序可以处理并完成该任务。
此模式的本质是执行实际工作的实体独立于流程引擎,并通过轮询流程引擎的API来接收工作项。这样做有以下好处:
- 跨越系统边界:外部工作者不需要运行在相同的Java进程中、相同的机器上、相同的集群中。所需要的只是它可以访问流程引擎的API(通过REST或Java)。由于轮询模式,工作者不需要公开任何接口以供流程引擎访问。
- 跨越技术边界:外部工作者不需要在Java中实现。相反,可以使用任何最适合执行工作项的技术,以及可用于访问流程引擎的API(通过REST或Java)的技术。
- 专业工作人员:外部工作人员不需要是通用应用程序。每个外部任务实例接收标识要执行的任务的性质的主题名称。工作人员只能轮询他们可以处理的主题的任务。
- 细粒度扩展:如果有高负载集中在服务任务处理上,则各个主题的外部工作者的数量可以独立于流程引擎进行扩展。
- 独立维护:工作者(Workers )可以在不中断操作的情况下独立于流程引擎进行维护。例如,如果特定主题的工作人员停机(例如,由于更新),则对流程引擎没有直接影响。这样的工作者的外部任务的执行被适度降级:它们被存储在外部任务列表中,直到外部工作者恢复操作。
2、处理外部任务
要处理外部任务,必须在BPMN XML中声明它们。在运行时,可以通过Java和REST API访问外部任务实例。下面解释了API的概念,并重点介绍了Java API。通常,REST API更适合这种情况,尤其是在使用不同技术实现在不同环境中运行的Workers时。
在流程定义的BPMN XML中,通过使用属性camunda:type和camunda:topic,可以将服务任务声明为由外部工作者执行。例如,服务任务Validate Address可以配置为提供主题AddressValidation的外部任务实例,如下所示:
<serviceTask id="validateAddressTask"
name="Validate Address"
camunda:type="external"
camunda:topic="AddressValidation" />
可以使用表达式而不是常数值来定义主题名称。
此外,其他类似服务任务的元素,如发送任务、业务规则任务和抛出消息事件,都可以使用外部任务模式来实现。有关详细信息,请参阅BPMN 2.0实现参考。
2.1、错误事件定义
外部任务允许定义引发指定BPMN错误的错误事件。这可以通过向任务的定义中添加camunda:errorEventDefinition扩展元素来完成。与bpmn:errorEventDefinition相比,camunda:errorEventDefinition元素接受一个额外的表达式属性,该属性支持任何JUEL表达式。在表达式中,您可以通过键externalTask访问ExternalTaskEntity对象,该键为errorMessage、errorDetails、workerId、retries等提供getter方法。
在调用ExternalTaskService#complete和ExternalTaskService#handleFailure时计算表达式。如果表达式的计算结果为true,则实际的方法执行将被取消,并通过引发相应的BPMN错误来替换。此错误可能会被错误边界事件捕获。这意味着错误事件定义可以在成功和失败场景中使用——即使任务成功完成,您仍然可以决定抛出BPMN错误。
<serviceTask id="validateAddressTask"
name="Validate Address"
camunda:type="external"
camunda:topic="AddressValidation" >
<extensionElements>
<camunda:errorEventDefinition id="addressErrorDefinition"
errorRef="addressError"
expression="${externalTask.getErrorDetails().contains('address error found')}" />
</extensionElements>
</serviceTask>
有关外部任务的错误事件定义功能的更多信息,请参见表达式语言用户指南.中介绍了RPA业务流程方案在外部任务中的具体使用。Camunda平台RPA网桥.
2.2、Rest API
请参见REST API文档 了解如何通过HTTP访问API操作。
https://docs.camunda.org/rest/camunda-bpm-platform/7.19/#tag/External-Task
2.2.1、获取和锁定外部任务的长轮询
无论请求的信息是否可用,服务器都会立即响应普通的HTTP请求。这不可避免地导致客户端必须执行多个重复请求直到信息可用(轮询)的情况。就资源而言,这种方法显然是昂贵的。
在长轮询的帮助下,如果没有外部任务可用,服务器将挂起请求。一旦发生新的外部任务,就重新激活请求并执行响应。暂停仅限于可配置的时间段(超时)。
长轮询大大减少了请求的数量,并使服务器和客户端都能更有效地使用资源。
另请参阅REST API文档.https://docs.camunda.org/rest/camunda-bpm-platform/7.19/#tag/External-Task/operation/fetchAndLock
2.2.2、唯一工作程序请求
默认情况下,多个workers 工人可以使用同一个workerId。为了确保服务器端的workerId唯一性,可以激活“唯一工人请求”标志。此配置标志仅影响长轮询请求,而不影响普通的“获取和锁定”请求。如果“唯一工作人员请求”标志已激活,则在收到新请求时,将取消具有相同workerId的挂起请求。
为了启用“Unique Worker Request”标志,需要通过将上下文参数fetch and lock Unique Worker Request设置为true来调整引擎休息工件中包含的引擎休息/WEB-INF/WEB.xml文件。请考虑以下配置片段:
<!-- ... -->
<context-param>
<param-name>fetch-and-lock-unique-worker-request</param-name>
<param-value>true</param-value>
</context-param>
<!-- ... -->
2.3、Java API
外部任务的Java API的入口点是ExternalTaskService。可以通过ProcessEngine.GetExternalTaskService()访问它。
下面是一个交互示例,它获取10个任务,在一个循环中处理这些任务,并且对于每个任务,要么完成任务,要么将其标记为失败。
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
try {
String topic = task.getTopicName();
// work on task for that topic
...
// if the work is successful, mark the task as completed
if(success) {
externalTaskService.complete(task.getId(), variables);
}
else {
// if the work was not successful, mark it as failed
externalTaskService.handleFailure(
task.getId(),
"externalWorkerId",
"Address could not be validated: Address database not reachable",
1, 10L * 60L * 1000L);
}
}
catch(Exception e) {
//... handle exception
}
}
以下部分更详细地介绍了与ExternalTaskService的不同交互。
2.3.1、获取外部任务
为了实现轮询工作者,可以使用方法ExternalTaskService#FetchAndLock执行获取操作。此方法返回一个Fluent构建器,该构建器允许定义一组主题来获取任务。考虑以下代码片段:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L)
.topic("ShipmentScheduling", 120L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
// work on task for that topic
...
}
此代码最多获取AddressValidation和ShipmentScheduling主题的10个任务。结果任务专为ID为ExternalWorkerID的员工锁定。锁定是指在从获取时间开始的一段时间内为该工作线程保留任务,并防止另一个工作线程在锁定有效时获取相同的任务。如果锁过期并且任务尚未完成,则不同的工作线程可以获取它,以便静默失败的工作线程不会无限期地阻止执行。单个主题获取说明中给出了确切的持续时间:AddressValidation任务锁定60秒(60L*1000L毫秒),而ShipmentScheduling任务锁定120秒(120L*1000L毫秒)。锁过期持续时间不应短于预期执行时间。它也不应该太高,如果这意味着超时时间太长,直到重试任务,以防工作者静默失败。
执行任务所需的变量可以与任务一起获取。例如,假设AddressValidation任务需要一个地址变量。使用此变量获取任务可能如下所示:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L).variables("address")
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
String address = (String) task.getVariables().get("address");
// work on task for that topic
...
}
然后,结果任务包含所请求变量的当前值。请注意,变量值是在外部任务执行的范围层次结构中可见的值。
为了获取所有变量,应省略对方法variables()的调用。
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
String address = (String) task.getVariables().get("address");
// work on task for that topic
...
}
为了启用序列化变量值(通常是存储自定义Java对象的变量)的反序列化,必须()调用EnableCustomObjectDeserialization。否则,一旦从变量映射中检索到序列化的变量,就会抛出一个异常,即对象未被反序列化。
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L)
.variables("address")
.enableCustomObjectDeserialization()
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
MyAddressClass address = (MyAddressClass) task.getVariables().get("address");
// work on task for that topic
...
}
2.3.2、外部任务优先级
外部任务优先级划分与作业优先级划分类似。同样的问题也存在于饥饿中,这是应该考虑的。有关更多详细信息,请参见工作优先级.
为外部任务配置优先级。如何在配置中启用和禁用外部任务优先级。可以在流程引擎配置上设置两个相关的配置属性:
productionPrioritydExternalTasks:控制流程引擎是否为外部任务分配优先级。默认值为true。如果不需要优先级,则可以将流程引擎配置属性producePrioritydExternalTasks设置为false。在这种情况下,所有外部任务的优先级都为0。有关如何指定外部任务优先级以及流程引擎如何分配这些优先级的详细信息,请参阅以下“指定外部任务优先权”部分。
2.2.3、指定外部任务优先级
外外部任务优先级可以在BPMN模型中指定,也可以在运行时通过API覆盖。
外部任务优先级可以在流程或活动级别分配。为此,可以使用Camunda扩展属性Camunda:taskPriority。
为了指定优先级,同时支持常数值和表达式。使用常数值时,会将相同的优先级分配给流程或活动的所有实例。另一方面,表达式允许为流程或活动的每个实例分配不同的优先级。表达式的计算结果必须为Java长范围内的数字。具体值可以是复杂计算的结果,并基于用户提供的数据(来自任务表单或其他来源)。
在流程实例级别配置外部任务优先级时,camunda:taskPriority属性需要应用于bpmn<process…>要素:
<bpmn:process id="Process_1" isExecutable="true" camunda:taskPriority="8">
...
</bpmn:process>
其效果是,流程中的所有外部任务都继承相同的优先级(除非在本地被覆盖)。上面的示例显示了如何使用常数值来设置优先级。这样,相同的优先级将应用于流程的所有实例。如果不同的流程实例需要以不同的优先级执行,则可以使用表达式:
<bpmn:process id="Process_1" isExecutable="true" camunda:taskPriority="${order.priority}">
...
</bpmn:process>
在上述示例中,基于变量顺序的属性优先级来确定优先级。
服务任务级别的优先级。在服务任务级别配置外部任务优先级时,camunda:taskPriority属性需要应用于bpmn<serviceTask…>要素服务任务必须是具有属性camunda:type=“external”的外部任务。
...
<serviceTask id="externalTaskWithPrio"
camunda:type="external"
camunda:topic="externalTaskTopic"
camunda:taskPriority="8"/>
...
其效果是为定义的外部任务设置优先级(覆盖流程TaskPriority)。上面的示例显示了如何使用常数值来设置优先级。这样,在流程的不同实例中,将相同的优先级应用于外部任务。如果不同的流程实例需要以不同的外部任务优先级执行,则可以使用表达式:
...
<serviceTask id="externalTaskWithPrio"
camunda:type="external"
camunda:topic="externalTaskTopic"
camunda:taskPriority="${order.priority}"/>
...
在上述示例中,基于变量顺序的属性优先级来确定优先级。
2.2.4、获取具有优先级的外部任务
要根据优先级获取外部任务,可以使用带有参数usePriority的重载方法ExternalTaskService#FetchAndLock。没有布尔参数的方法任意返回外部任务。如果给定参数,则返回的外部任务按降序排列。请参阅以下有关外部任务优先级的示例:
List<LockedExternalTask> tasks =
externalTaskService.fetchAndLock(10, "externalWorkerId", true)
.topic("AddressValidation", 60L * 1000L)
.topic("ShipmentScheduling", 120L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
// work on task for that topic
...
}
2.2.5、完成任务
在获取并执行所请求的工作之后,工作者可以通过调用ExternalTaskService#complete方法来完成外部任务。工作线程只能完成它之前获取并锁定的任务。如果任务在此期间已被其他工作线程锁定,则会引发异常。
2.2.6、错误事件
外部任务可以包括错误事件定义 如果错误事件的表达式的计算结果为true,则可以取消#complete的执行。如果错误事件的表达式计算引发异常,则对#Complete的调用将因相同的异常而失败。
2.2.7、扩展外部任务上的锁
当外部任务被工作线程锁定时,可以通过调用ExternalTaskService#extendLock方法来延长锁定持续时间。工作线程可以指定更新超时的时间量(以毫秒为单位)。锁只能由拥有给定外部任务的锁的工作线程扩展。
2.2.8、任务失败报告
员工可能无法始终成功完成任务。在这种情况下,它可以使用ExternalTaskService#HandleFailure向流程引擎报告故障。与#Complete一样,#HandleFailure只能由拥有任务最新锁的工作线程调用。#handleFailure方法有四个额外的参数:errorMessage、errorDetails、retries和retryTimeout。错误消息可以包含对问题性质的描述,并且限制为666个字符。当再次获取或查询任务时,可以访问它。ErrorDetails可以包含完整的错误描述,并且长度不受限制。根据任务ID参数,可通过单独的方法ExternalTaskService#getExternalTaskErrorDetails访问错误详细信息。通过Retries和RetryTimout,工作人员可以指定重试策略。将重试次数设置为大于0的值时,可以在RetryTimeout过期后再次获取任务。 将“重试次数”设置为0时,无法再获取任务,并为该任务创建突发事件。
考虑以下代码片段:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L).variables("address")
.execute();
LockedExternalTask task = tasks.get(0);
// ... processing the task fails
externalTaskService.handleFailure(
task.getId(),
"externalWorkerId",
"Address could not be validated: Address database not reachable", // errorMessage
"Super long error details", // errorDetails
1, // retries
10L * 60L * 1000L); // retryTimeout
// ... other activities
externalTaskService.getExternalTaskErrorDetails(task.getId());
对于锁定的任务报告失败,以便可以在10分钟后再次重试。流程引擎本身不会减少重试次数。相反,可以通过在报告失败时将RETRIES设置为Task.getRetries()-1来实现此类行为。
在需要错误详细信息时,使用单独的方法从服务中查询它们。
2.2.9、错误事件
外部任务可以包括错误事件定义 这可以在错误事件的表达式计算为true时取消#HandleFailure的执行。如果错误事件的表达式计算引发异常,则此表达式将被视为计算结果为false。
2.2.10、报告BPMN错误
由于某些原因,在执行过程中可能会出现业务错误。在这种情况下,工作线程可以使用ExternalTaskService#handleBPMNerror向流程引擎报告BPMN错误。与#Complete或#HandleFailure一样,它只能由拥有任务最新锁的工作线程调用。#handleBPMnError方法有一个额外的参数:errorCode。错误代码标识预定义的错误。如果给定的ErrorCode不存在,或者没有定义边界事件,则当前活动实例简单地结束,并且不处理错误。
请参见以下示例:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L).variables("address")
.execute();
LockedExternalTask task = tasks.get(0);
将传播错误代码为BPMN-ERROR的BPMN错误。如果存在具有此错误代码的边界事件,则将捕获并处理BPMN错误。错误消息和变量是可选的。它们可以提供有关错误的其他信息。如果捕捉到BPMN错误,变量将被传递给执行。
2.2.11、查询任务
可以通过ExternalTaskService#createExternalTaskQuery查询外部任务。与#fetchandlock相反,这是一个不设置任何锁的读取查询。
2.2.12、管理操作
其他管理操作包括ExternalTaskService#Unlock、ExternalTaskService#SetRetries和ExternalTaskService#SetPriority,用于清除当前锁定、设置重试次数以及设置外部任务的优先级。当任务的剩余重试次数为0且必须手动恢复时,设置重试次数非常有用。使用最后一种方法,可以为较重要的外部任务设置较高的优先级,或者为不太重要的外部任务设置较低的优先级。
还有ExternalTaskService#setRetriesSync和ExternalTaskService#setRetriesAsync操作,用于同步或异步设置多个外部任务的重试。