Flowable 事务与并发控制:保证流程一致性的关键机制
【免费下载链接】flowable-userguideFlowable最新中文文档,ai自动生成BPM体验地址:http://flow.je4.cn/#/login项目地址: https://gitcode.com/gh_mirrors/fl/flowable-userguide
一、Flowable 事务模型:默认的"等待状态"机制
Flowable 是当前最流行的开源工作流引擎之一,它天然以事务方式执行 BPMN 流程。当你启动一个流程实例、完成任务或发送信号时,Flowable 会沿着流程图的每一条活跃路径做深度优先遍历,一直推进到**等待状态(Wait State)**才停下来。
所谓等待状态,就是"稍后"才执行的任务节点,比如用户任务、接收消息任务、定时器事件等。到达等待状态后,Flowable 会把当前执行状态持久化到数据库,等待下一次触发。这一点非常重要:一次 API 调用对应一个事务单元,这个单元要么全部成功,要么全部回滚。
上图展示了事务边界的两种切法:左侧是默认的同步方式,用户任务"补充收货地址"和服务任务"校验地址"处于同一事务中,一旦校验失败,任务完成操作也会回滚,用户任务依然留在数据库里;右侧则通过异步延续,把"生成发票"拆成了独立的事务。
二、异步延续:自定义事务边界的核心武器
很多业务场景并不希望"一损俱损"。例如用户任务完成后要生成发票并发给客户,如果生成发票失败,用户任务的完成结果不应该被回滚。此时就需要**异步延续(Async Continuation)**来拆事务。
只需在 BPMN 节点上添加flowable:async="true"扩展属性即可:
<serviceTask id="service1" name="Generate Invoice" flowable:class="my.custom.Delegate" flowable:async="true" />配置了异步延续后,Flowable 会在后台创建一个Job(作业)并持久化到数据库,由异步执行器(Async Executor)的线程池取出来执行。flowable:async可以用于task、serviceTask、scriptTask、sendTask、userTask、subProcess、callActivity等几乎所有任务类型。
📌适用场景:耗时操作、与外部系统交互、非核心业务逻辑,都适合拆成异步事务,避免长时间占用数据库连接和调用线程。
三、失败重试机制:让流程更健壮
Flowable 默认配置下,一个 Job 执行异常时会自动重试 3 次。但对于真实业务,3 次往往不够灵活,你可以通过flowable:failedJobRetryTimeCycle自定义重试次数和重试间隔(遵循 ISO 8601 时间格式):
<serviceTask id="failingServiceTask" flowable:async="true" flowable:class="org.flowable.engine.test.jobexecutor.RetryFailingDelegate"> <extensionElements> <flowable:failedJobRetryTimeCycle>R5/PT7M</flowable:failedJobRetryTimeCycle> </extensionElements> </serviceTask>上面的配置表示:重试 5 次,每次间隔 7 分钟。
⚠️重要提醒:如果服务带有非事务性副作用(比如调用外部支付接口),重试可能造成副作用重复执行。这类场景必须做好幂等设计。
四、独占任务(Exclusive Jobs):并发安全的默认防线
为什么要独占?
看下面这个经典场景:并行网关后跟着三个异步服务任务,它们各自生成一个 Job,可能被线程池同时取走执行。当三个分支同时到达并行合并网关时,每个事务都"看不到"其他分支的到来,于是都认为需要等待其他分支——结果谁也不会继续,流程实例永远卡死在合并网关。
乐观锁兜底
Flowable 用乐观锁解决这个问题:多个事务基于同一行数据做决策时,都会递增该数据库行的版本号。先提交者获胜,其他事务抛出乐观锁异常,随后由 Job 执行器自动重试,最终顺利通过合并网关。
独占任务如何工作
从 Flowable 6 开始,所有异步延续和定时器事件默认都是独占的(Exclusive)。独占意味着:同一流程实例的独占 Job 绝不会并发执行——执行器会一次性获取同一流程实例的所有独占 Job,交给同一个工作线程串行执行。
如果确实想并发,可以显式关闭:
<serviceTask id="service" flowable:expression="${myService.performBooking(hotel, dates)}" flowable:async="true" flowable:exclusive="false" />🎯性能误区澄清:独占只限制"同一流程实例"内的 Job 串行,不同流程实例的 Job 依然并行执行。反而因为节点亲和性,后续 Job 所需的流程数据已在本地缓存,通常整体吞吐更优。
五、异步执行器:V6 时代的作业调度核心
从 Flowable 6 开始,异步执行器是唯一可用的 Job 执行机制,它采用线程池 + 数据库表分区的架构:
- 定时器作业(如边界定时事件)存放在
ACT_RU_TIMER_JOB,到期后转为异步作业; - 异步作业(
flowable:async="true")写入ACT_RU_JOB表,提交后由事务监听器触发执行; - 重试超过上限的"死作业"进入
ACT_RU_DEADLETTER_JOB死信表,供管理员人工排查; - 挂起流程相关的作业进入
ACT_RU_SUSPENDED_JOB,让获取查询保持精简高效。
常用配置项(通过SpringProcessEngineConfiguration设置):
| 配置项 | 默认值 | 说明 |
|---|---|---|
asyncExecutorCorePoolSize | 2 | 线程池核心线程数 |
asyncExecutorMaxPoolSize | 10 | 线程池最大线程数 |
asyncExecutorNumberOfRetries | 3 | 重试次数上限,超限进入死信表 |
asyncExecutorAsyncJobLockTimeInMillis | 5 分钟 | 作业锁定时间,防止多节点重复执行 |
asyncExecutorResetExpiredJobsInterval | 60 秒 | 清理过期锁定的周期 |
六、BPMN 事务子流程与补偿:长事务的正确姿势
事务子流程的三种结局
BPMN 的**事务子流程(Transaction Sub-process)**把一组活动圈成一个业务事务,有三种结局:
- 成功:正常流出,后续可用补偿事件撤销;
- 取消:到达取消结束事件,触发补偿后从取消边界事件流出;
- 风险终止:错误事件未被捕获,直接终止且不执行补偿。
与 ACID 事务的本质区别
务必分清:BPMN 事务子流程 ≠ 数据库 ACID 事务。ACID 事务短小精悍、支持回滚;而 BPMN 事务可能持续数小时甚至数月(比如等待用户审批、等待订单履约),天然跨越多个 ACID 事务,因此失去隔离性和传统回滚能力。
解决之道是补偿(Compensation):为已成功执行的活动配置补偿处理器,当事务取消时,逆序调用这些补偿处理器撤销业务影响,例如"取消酒店预订""取消航班预订"。
💡 更深入的事务子流程、补偿边界事件与补偿处理器实现,可参考官方中文文档 ch07b-BPMN-Constructs.adoc,异步执行器细节见 ch18-Advanced.adoc。
七、实践建议:新手避坑清单 ✅
- 优先保持默认:异步延续默认独占,绝大多数场景直接使用即可,不要轻易关闭;
- 拆分要克制:只有真正需要独立事务边界的环节才用
flowable:async="true",过度拆分反而增加复杂度; - 做好幂等:凡是配置了重试的异步任务,业务逻辑必须能容忍重复执行;
- 监控死信表:定期检查
ACT_RU_DEADLETTER_JOB,及时发现反复失败的任务; - 补偿尽早设计:涉及跨系统、长周期的业务流程,从建模阶段就要规划补偿处理器;
- 关注锁超时:长时间运行的流程实例,留意定时器锁与作业锁的过期重置配置。
总结
Flowable 通过事务边界的默认划分保证单元一致性,通过异步延续灵活拆分长流程,通过独占任务 + 乐观锁化解并行网关的并发冲突,再借助异步执行器完成可靠调度,最后用事务子流程 + 补偿支撑跨系统长事务。理解这五层机制,你就能设计出既高性能又高一致性的工作流应用。
如果你想动手实践,可以参考仓库中的 SQL 建表脚本(sql/create)和完整用户指南(V6.4.2/docs/userguide/src/zh_CN),边看边跑,很快就能掌握 Flowable 事务与并发控制的精髓!
【免费下载链接】flowable-userguideFlowable最新中文文档,ai自动生成BPM体验地址:http://flow.je4.cn/#/login项目地址: https://gitcode.com/gh_mirrors/fl/flowable-userguide
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考