工作流Activiti框架的事务和并发!流程引擎中异步和排他操作详细解析

简介: 本篇文章介绍了在工作流的场景下项目中的事务操作和并发操作。主要内容包括异步操作和排他任务。通过这篇文章,可以了解到在工作流Activiti框架中处理事务操作的具体方式以及排他任务机制保证的在同一个流程实例中的Job不会并发执行。在阅读完这篇文章,基本上就能清楚地了解了工作流Activiti框架中的并发处理了。

事务和并发

异步操作

  • Activiti通过事务方式执行流程,可以根据需求定制
  • Activiti处理事务:

    • 如果触发了Activiti的操作(开始流程,完成任务,触发流程继续执行),activiti会推进流程,直到每个分支都进入等待状态
    • 抽象的说,会从流程图执行深度优先搜索,如果每个分支都遇到等待状态,就会返回
    • 等待状态是稍后需要执行任务,Activiti会把当前状态保存到数据库中,然后等待下一次触发
    • 触发可能来自外部,比如用户任务或接收到一个消息,也可能来自Activiti本身(定时器事件)

在这里插入图片描述
流程包含用户任务,服务任务和定时器事件
完成用户任务和校验地址是在同一个工作单元中,两者的成功和失败是原子性的.意味着如果服务任务抛出异常,要回滚当前事务,这样流程会退回到用户任务,用户任务就依然在数据库里
这就是activiti默认的行为.在(1)中应用或客户端线程完成任务.这会执行服务,流程推进,直到遇到一个等待状态,就是定时器(2),然后它会返回给调用者(3),并提交事务(如果事务是由Activiti开启的)

  • 有时需要自定义控制流程中事务的边界,把业务逻辑包裹在一起.这就需要使用异步执行:

在这里插入图片描述
完成了用户任务,生成一个发票,把发票发送给客户
生成发票不在同一个工作单元内了.如果生成发票出错不需要对用户任务进行回滚
Activiti实现的是完成用户任务(1),提交事务,返回给调用者应用.然后在后台的线程中,异步执行生成发票.
后台线程就是Activiti的Job执行器(一个线程池)周期对数据库的Job进行扫描:当到达"generate invoice"任务,为Activiti创建一个稍后执行的Job"消息",并保存到数据库.Job会被Job执行器获取并执行.也会给本地Job执行器一个提醒,告诉有一个新Job,来增加性能

  • 要想使用这个特性,要使用activiti:async="true" 扩展
<serviceTask id="service1" name="Generate Invoice" activiti:class="my.custom.Delegate" activiti:async="true" />
  • activiti:async可以使用到以下BPMN任务类型中:

    • task
    • serviceTask,
    • scriptTask
    • businessRuleTask
    • sendTask
    • receiveTask
    • userTask
    • subProcess
    • callActivity
  • 对于userTask,receiveTask和其他等待状态,异步执行的作用是让开始流程监听器运行在一个单独的线程或者事务中

排他任务

  • 从Activiti 5.9开始 ,JobExecutor能保证同一个流程实例中的Job不会并发执行
排他任务的产生背景

在这里插入图片描述

  • 一个并行网关,后面有三个服务任务,都设置为异步执行:

    • 这样会添加三个job到数据库里.一旦job进入数据库,就可以被jobExecutor执行了.JobExecutor会获取job,代理到工作线程的线程池中,在那里真正执行job
    • 就是说,使用异步执行,可以把任务分配给这个线程池(在集群环境,可能会使用多个线程池)
    • 产生一致性问题:

      • 考虑一下服务任务后的汇聚:当服务任务完成后,到达并发汇聚节点,需要决定是等待其他分支,还是继续向下执行
      • 就是说,对每个到达并行汇聚的分支,都需要判断是继续还是等待其他分支的一个或多个分支
  • 为什么会产生这样的问题:

    • 因为服务任务配置成使用异步执行,可能相关的job都在同一时间被获取,被JobExecutor分配给不同的工作线程执行
    • 结果是,三个单独的服务执行使用的事务在到达并发汇聚时可能重叠:

      • 如果出现了这个问题,这些事务是互相不可见的,其他事务同时到达了相同的并发汇聚,假设都在等待其他分支
      • 然而,每个事务都假设在等待其他分支,所以没有分支会越过并发汇聚继续执行,流程实例会一直在等待状态,无法继续执行
  • Activiti解决这个问题方式:

    • Activiti使用了乐观锁:

      • 当基于判断的数据看起来不是最新的时候 (因为其他事务可能在提交之前进行了修改,会在每个事务里增加数据库同一行的版本),这个时候,第一个提交的事务会成功,其他会因为乐观锁异常导致失败
      • 这就解决了上面流程的问题:

        • 如果多个分支同步到达并行汇聚,会假设都在登录,并增加父流程的版本号(流程实例)然后尝试提交
        • 第一个分支会成功提交,其他分支会因为乐观锁导致失败
        • 因为流程是被job触发的,Activiti会尝试在等待一段时间后尝试执行同一个job,这段时间可以同步网关的状态
  • Activiti乐观锁是一个很好的解决方案吗?

    • 乐观锁允许Activiti避免非一致性,确定流程不会"堵在汇聚网关": 或者所有分支都通过网关,或者数据库中的job正在尝试通过
    • 虽然这是一个对于持久性和一致性的完美解决方案,但对于上层来说不一定是期望的行为:

      • Activiti只会对同一个job重试估计次数(默认配置为3).之后,job还会在数据库里,但是不会再重试了.意味着这个操作必须手工执行job的触发
      • 如果job有非事务方面的效果,不会因为失败的事务回滚:如果“预定演唱会门票”服务没有与Activiti共享事务,重试job可能导致我们预定了过多门票
  • 针对这些问题,在Activiti中推出了新的概念:排他job
排他Job
  • 对于一个流程实例,排他任务不能同时执行两个
  • 考虑上面的流程:如果我们把服务任务申请为排他任务,JobExecutor会保证对应的job不会并发执行.
  • 会保证无论什么时候获取一个流程实例的排他任务,都会把同一个流程实例的其他任务都取出来,放在同一个工作线程中执行.保证job是顺序执行的
  • 从activiti 5.9开始,排他任务已经是默认配置.所以异步执行和定时器事件默认都是排他任务
  • 如果你想把job设置为非排他,可以使用activiti:exclusive="false" 进行配置:
<serviceTask id="service" activiti:expression="${myService.performBooking(hotel, dates)}" activiti:async="true" activiti:exclusive="false" />
  • 排他任务没有性能问题:

    • 在高负载的情况下性能是个问题,高负载意味着JobExecutor的所有工作线程都一直在忙碌着
    • 使用排他任务,Activiti可以简单的分布不同的负载.排他任务意味着同一个流程实例的异步执行会由相同的线程顺序执行
    • 但是要考虑:如果有多个流程实例时.所有其他流程实例的job也会分配给其他线程同步执行
    • 意味着虽然Activiti不会同时执行一个流程实例的排他job,但是还会同步执行多个流程实例的异步执行
    • 通过一个总体的预测,在大多数场景下,排他任务都会让单独的实例运行的更迅速.而且,对于同一流程实例中的job,需要用到的数据也会利用执行的集群节点的缓存.如果任务没有在同一个节点执行,数据就必须每次从数据库重新读取了

流程实例授权

  • 默认所有人在部署的流程定义上启动一个新流程实例,通过流程初始化授权功能定义的用户和组,web客户端可以限制哪些用户可以启动一个新流程实例
  • Activiti引擎不会校验授权定义: 这个功能只是为减轻web客户端开发者实现校验规则的难度
  • 设置方法与用户任务用户分配类似,用户或组可以使用activiti:potentialStarter标签分配为流程的默认启动者:
 <process id="potentialStarter">
     <extensionElements>
       <activiti:potentialStarter>
         <resourceAssignmentExpression>
           <formalExpression>group2, group(group3), user(user3)</formalExpression>
         </resourceAssignmentExpression>
       </activiti:potentialStarter>
     </extensionElements>
   <startEvent id="theStart"/>
   ...

user(user3)是直接引用了用户user3,group(group3)是引用了组group3.如果没显示设置,默为群组

  • 也可以使用process标签的属性activiti:candidateStarterUsersactiviti:candidateStarterGroups
 <process id="potentialStarter" activiti:candidateStarterUsers="user1, user2"
                                activiti:candidateStarterGroups="group1">
      ...
  

可以同时使用这两个属性

  • 定义流程初始化授权后,开发者可以使用如下方法获得授权定义.可以获得给定的用户能够启动哪些流程定义:
 processDefinitions = repositoryService.createProcessDefinitionQuery().startableByUser("userxxx").list();
  • 可以获得指定流程定义设置的潜在启动者对应的IdentityLink:
 identityLinks = repositoryService.getIdentityLinksForProcessDefinition("processDefinitionId"); 
  • 获得可以启动给定流程的用户列表的示例:
  List<User> authorizedUsers =  identityService().createUserQuery().potentialStarter("processDefinitionId").list(); 
  • 获得可以启动给定流程配置的群组的示例:
 List<Group> authorizedGroups =  identityService().createGroupQuery().potentialStarter("processDefinitionId").list();  

数据对象

  • BPMN提供了一种功能,可以在流程定义或子流程中定义数据对象
  • 根据BPMN规范,流程定义可以包含复杂XML结构,可以导入XSD定义
  • 对于Activiti来说 ,作为Activiti首次支持的数据对象, 可以支持如下的XSD类型
    <dataObject id="dObj1" name="StringTest" itemSubjectRef="xsd:string"/>
    <dataObject id="dObj2" name="BooleanTest" itemSubjectRef="xsd:boolean"/>
    <dataObject id="dObj3" name="DateTest" itemSubjectRef="xsd:datetime"/>
    <dataObject id="dObj4" name="DoubleTest" itemSubjectRef="xsd:double"/>
    <dataObject id="dObj5" name="IntegerTest" itemSubjectRef="xsd:int"/>
    <dataObject id="dObj6" name="LongTest" itemSubjectRef="xsd:long"/>
  • 数据对象定义会自动转换为流程变量,名称与name属性对应
  • 除了数据对象的定义之外,Activiti支持使用扩展元素来为这个变量赋予默认值:
<process id="dataObjectScope" name="Data Object Scope" isExecutable="true">
          <dataObject id="dObj123" name="StringTest123" itemSubjectRef="xsd:string">
            <extensionElements>
              <activiti:value>Testing123</activiti:value>
            </extensionElements>
          </dataObject>
相关文章
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
280 3
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
1078 3
|
人工智能 API 开发者
HarmonyOS Next~鸿蒙应用框架开发实战:Ability Kit与Accessibility Kit深度解析
本书深入解析HarmonyOS应用框架开发,聚焦Ability Kit与Accessibility Kit两大核心组件。Ability Kit通过FA/PA双引擎架构实现跨设备协同,支持分布式能力开发;Accessibility Kit提供无障碍服务构建方案,优化用户体验。内容涵盖设计理念、实践案例、调试优化及未来演进方向,助力开发者打造高效、包容的分布式应用,体现HarmonyOS生态价值。
1019 27
|
人工智能 自然语言处理 搜索推荐
ViDoRAG:开源多模态文档检索框架,多智能体推理+图文理解精准解析文档
ViDoRAG 是阿里巴巴通义实验室联合中国科学技术大学和上海交通大学推出的视觉文档检索增强生成框架,基于多智能体协作和动态迭代推理,显著提升复杂视觉文档的检索和生成效率。
1172 8
ViDoRAG:开源多模态文档检索框架,多智能体推理+图文理解精准解析文档
|
机器学习/深度学习 人工智能 Java
Java机器学习实战:基于DJL框架的手写数字识别全解析
在人工智能蓬勃发展的今天,Python凭借丰富的生态库(如TensorFlow、PyTorch)成为AI开发的首选语言。但Java作为企业级应用的基石,其在生产环境部署、性能优化和工程化方面的优势不容忽视。DJL(Deep Java Library)的出现完美填补了Java在深度学习领域的空白,它提供了一套统一的API,允许开发者无缝对接主流深度学习框架,将AI模型高效部署到Java生态中。本文将通过手写数字识别的完整流程,深入解析DJL框架的核心机制与应用实践。
1015 3
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
消息中间件 JavaScript 前端开发
最细最有条理解析:事件循环(消息循环)是什么?为什么JS需要异步
度一教育的袁进老师谈到他的理解:单线程是异步产生的原因,事件循环是异步的实现方式。 本质是因为渲染进程因为计算机图形学的限制,只能是单线程。所以需要“异步”这个技术思想来解决页面阻塞的问题,而“事件循环”是实现“异步”这个技术思想的最主要的技术手段。 但事件循环并不是全部的技术手段,比如Promise,虽然受事件循环管理,但是如果没有事件循环,单一Promise依然能实现异步不是吗? 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
174 0
|
分布式计算 Java 应用服务中间件
NettyIO框架的深度技术解析与实战
【10月更文挑战第13天】Netty是一个异步事件驱动的网络应用程序框架,由JBOSS提供,现已成为Github上的独立项目。
275 0

热门文章

最新文章

推荐镜像

更多
  • DNS