# demo11 **Repository Path**: Flowable_BPMN/demo11 ## Basic Information - **Project Name**: demo11 - **Description**: 事务与并发: 1、异步延续 2、失败重试 3、排他作业 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 3 - **Created**: 2019-02-02 - **Last Updated**: 2023-12-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ------ 环境: [jkd8+]() [mysql5.6+]() [flowable6.4.0]() # 1、异步延续 ## 1.1 描述 (Asynchronous Continuations) Flowable以事务方式执行流程,并可按照你的需求配置。让我们从Flowable一般如何为事务划分范围开始介绍。如果Flowable被触发(启动流程,完成任务,为执行发送信号),Flowable将沿流程执行,直到到达每个执行路径的等待状态。更具体地说,它以深度优先方式搜索流程图,并在每个执行分支都到达等待状态时返回。等待状态是“之后”再执行的任务,也就是说着Flowable将当前执行持久化,并等待再次触发。触发可以来自外部来源如用户任务或消息接受任务,也可以来自Flowable自身如定时器事件。以下面的图片说明: ![async.example.no.async](https://www.flowable.org/docs/userguide/images/async.example.no.async.PNG) 这是一个BPMN流程的片段,有一个用户任务、一个服务任务,与一个定时器事件。用户任务的完成操作与验证地址(validate address)在同一个工作单元内,因此需要原子性地(atomically)成功或失败。这意味着如果服务任务抛出了异常,我们会想要回滚当前事务,以便执行返回到用户任务,并希望用户任务仍然保存在数据库中。这也是Flowable的默认行为。在(1)中,应用或客户端线程完成任务。在相同的线程中,Flowable执行服务并继续,直到到达等待状态,在这个例子中,是定时器事件(2)。然后将控制权返回至调用者(3),同时提交事务(如果事务由Flowable开启)。 在有的情况下,我们不想要这样。有时我们需要在流程中自定义地控制事务边界,以便为工作的逻辑单元划分范围。这就需要使用异步延续。考虑下面的流程(片段): ![async.example.async](https://www.flowable.org/docs/userguide/images/async.example.async.PNG) 完成用户任务,生成发票,并将发票发送给客户。这次发票的生成不再是同一个工作单元的一部分,因此我们不希望当发票生成失败时,回滚用户任务。所以我们希望Flowable做的,是完成用户任务(1),提交事务,并将控制权返回给调用程序。然后我们希望在后台线程中,异步地生成发票。这个后台线程就是Flowable作业执行器(事实上是一个线程池),它周期性地将作业保存至数据库。因此在幕后,当到达"generate invoice(生成发票)"任务时,Flowable会创建“消息”作业并将其持久化到数据库中,用于继续执行流程。这个作业之后会被作业执行器选中并执行。Flowable也会向本地的作业执行器进行提示,告知其有新作业到来,以提升性能。 要使用这个特性,可以使用*flowable:async="true"*扩展。因此,一个示例的服务任务会像是这样: ```xml ``` 可以为下列BPMN任务类型指定*flowable:async*:任务,服务任务,脚本任务,业务规则任务,发送任务,接收任务,用户任务,子流程,调用活动。 对于用户任务、接收任务与其他等待状态来说,异步操作允许我们在一个独立的线程/事务中启动执行监听器。 ## 1.2 示例 ![](./images/asynchronouscontinuations.png) ## 1.3 测试--AsynchronousContinuationsTest - 部署 - 启动流程示例 - 让主线程睡眠,以便测试定时和异步任务 - 查看数据库 # 2、失败重试 ## 2.1描述 默认配置下,如果作业执行中有任何异常,Flowable将三次重试执行作业。对异步作业也是这样。需要更灵活的配置时可以使用这两个参数: - 重试的次数 - 重试的间隔 这两个参数可以通过`flowable:failedJobRetryTimeCycle`元素配置。这有一个简单的例子: ```xml R3/PT2M ``` 时间周期表达式与定时器事件表达式一样遵循ISO 8601标准。上面的例子会让作业执行器重试3次,并在每次重试前等待2分钟。 ## 2.2 示例 ![](./images/failretry.png) ​ ## 2.3 测试--FailRetryTest - 部署 - 启动流程示例 - 让主线程睡眠,以便测试定时和异步任务 - 查看数据库 # 3、排他作业 ## 3.1描述 从近期版本开始,JobExecutor确保同一个流程实例的作业永远不会并发执行。为什么这样? ##### 为什么使用排他作业? 考虑下面的流程定义: ![bpmn.why.exclusive.jobs](https://www.flowable.org/docs/userguide/images/bpmn.why.exclusive.jobs.png) 一个并行网关,之后是三个服务任务,都使用异步操作执行。其结果是在数据库中添加了三个作业。当作业储存在数据库后,就由JobExecutor处理。JobExecutor获取作业,并将其委托至工作线程的线程池,由它们实际执行作业。这意味着通过使用异步操作,可以将工作分发至线程池(在集群场景下,甚至会在集群中跨越多个线程池)。通常这都是好事。但也有其固有问题:一致性。考虑服务任务后的并行合并:当服务任务的执行完成时到达并行合并,并需要决定等待其他执行,还是需要继续向前。这意味着,对于每一个到达并行合并的分支,都需要选择继续执行,还是需要等待其他分支上的一个或多个其他执行。 为什么这是问题呢?这是因为服务任务配置为使用异步延续,有可能所有相应的作业都同时被作业执行器处理,并委托至不同的工作线程。结果是服务执行的事务,与到达并行合并的3个独立执行所在的事务会发生重叠。如果这样,每一个独立事务都“看不到”其他事物已经并发地到达了同样的并行合并,并因此判断自己需要等待其他事务。然而,如果每个事务都判断需要等待其他事务,在并行合并后不会有继续流程的事务,而流程实例也就会永远保持这个状态。 Flowable如何解决这个问题呢?Flowable使用乐观锁。每当需要基于数据进行判断,而数据可能不是最新值(因为其他事务可能在我们提交前修改了这个数据)时,我们确保会在每个事务中都增加同一个数据库记录行的版本号。这样,无论哪个事务第一个提交,都将成功,而其他的会抛出乐观锁异常并失败。这样就解决了上面流程中讨论的问题:如果多个执行并发到达并行合并,它们都判断需要等待,增加其父执行(流程实例)的版本号并尝试提交。无论哪个执行第一个提交,都可以成功提交,而其他的将会抛出乐观锁异常并失败。因为这些执行由作业触发,Flowable会在等待给定时间后,重试执行相同的作业,期望这一次通过这个同步的网关。 这是好的解决方案么?我们可以看到乐观锁使Flowable避免不一致状态。它确保了我们不会“在合并网关卡住”,也就是说:要么所有的执行都通过网关,要么数据库中的作业能确保可以重试通过它。然而,尽管这是一个持久化与一致性角度的完美解决方案,但从更高层次看,仍然不一定总是理想行为: - Flowable只会为同一个作业重试一个固定的最大次数(默认配置为*3*次)。在这之后,作业仍然保存在数据库中,但不会再重试。这就需要手动操作来触发作业。 - 如果一个作业有非事务性的副作用,则副作用将不会由于事务失败而回滚。例如,如果"book concert tickets(预定音乐会门票)"服务与Flowable不在同一个事务中,则重试执行作业将预定多张票。 ##### 什么是排他作业? 排他作业不能与同一个流程实例中的其他排他作业同时执行。考虑上面展示的流程:如果我们将服务任务都声明为排他的,则JobExecutor将确保相关的作业都不会并发执行。相反,它将确保不论何时从特定流程实例中获取了排他作业,都会从同一个流程实例中获取所有其他的排他作业,并将它们委托至同一个工作线程。这保证了作业的顺序执行。 如何启用这个特性?从近期版本开始,排他作业成为默认配置。所有异步操作与定时器事件都默认为排他的。另外,如果希望作业成为非排他的,可以使用`flowable:exclusive="false"`配置。例如,下面的服务任务是异步,但非排他的。 ```xml ``` 这是好的解决方案么?有很多人都在问这个问题。他们的顾虑是,这将阻止并行操作,因此会有性能问题。但也需要考虑以下两点: - 如果你是专家,并且知道你在做什么(并理解“为什么排他作业?”章节的内容),可以关掉排他。否则,对大多数用户来说,异步操作与定时器能够正常工作才更重要。 - 事实上不会有性能问题。只有在重负载下才会有性能问题。重负载意味着作业执行器的所有的工作线程都一直忙碌。对于排他作业,Flowable会简单的根据负载不同进行分配。排他作业意味着同一个流程实例的作业都将在同一个线程中顺序执行。但是请想一下:我们有不止一个流程实例。其他流程实例的作业将被委托至其他线程,并与本实例的作业并发执行。也就是说Flowable不会并发执行同一个流程实例的排他作业,但会并发执行多个实例。从总吞吐量角度来看,可以期望大多数场景下都可以保证实例很快地完成。此外,执行同一个流程实例中下一个作业所需的数据,已经缓存在同一个执行集群节点中。如果作业与节点没有关联关系,则可能需要重新从数据库中获取数据。 ## 3.2 流程图 ![](./images/exclusivejobs.png) ## 3.3 测试--ExclusiveJobsTest - 部署 - 启动流程示例 - 让主线程睡眠,以便测试定时和异步任务 - 查看数据库