工作流引擎乐观锁机制

乐观锁

盘古BPM可以用于多线程应用程序。在这种设置下,当多个线程同时与流程引擎交互时,可能会发生这些线程尝试对同一数据进行更改的情况。例如:两个线程尝试同时(同时)完成同一用户任务。这种情况是一种冲突:任务只能完成一次。

盘古BPM使用一种称为“乐观锁”(或乐观并发控制)的众所周知的技术来检测和解决此类情况。

本节分为两部分:第一部分介绍概念锁。如果您已经非常熟悉乐观锁定,则可以跳过本节。第二部分介绍了盘古BPM中乐观锁的用法。

什么是乐观锁定?

乐观锁定(也称为乐观并发控制)是一种用于并发控制的方法,用于基于事务的系统中。在读取的数据比更改的数据更频繁的情况下,乐观锁定是最有效的。许多线程可以同时读取相同的数据对象,而不会互相排斥。然后,在多个线程尝试同时更改同一数据对象的情况下,通过检测冲突并防止更新来确保一致性。如果检测到此类冲突,请确保只有一个更新成功,而所有其他更新失败。

假设我们有一个数据库表,其中包含以下条目:

 

上表显示了保存用户数据的一行。用户具有唯一的ID(主键),版本,名称和当前地址。

现在,我们构建一种情况,其中2个事务试图更新该条目,一个事务试图更改地址,另一个事务试图删除用户。预期的行为是,一旦一个事务成功,另一个事务中止,并显示一条错误,指示检测到并发冲突。然后,用户可以根据数据的最新状态决定重试事务:

 

如您在上图中所看到的,Transaction 1读取用户数据,对数据进行某些操作,删除用户然后提交。 Transaction 2在同一时间启动并读取相同的用户数据,并且还可以处理该数据。当Transaction 2尝试更新检测到冲突的用户地址(因为Transaction 1已删除的用户)。

检测到冲突是因为在Transaction 2执行更新时读取了用户数据的当前状态。当时,并发Transaction 1已经标记了要删除的行。数据库现在等待Transaction 1结束。结束后,Transaction 2可以继续。目前,该行已不存在,并且更新成功,但报告已更改了0行。应用程序可以对此做出反应并回滚,Transaction 2以防止该事务进行的其他更改生效。

应用程序(或使用它的用户)可以进一步决定是否Transaction 2应重试。在我们的示例中,交易将找不到用户数据并报告该用户已被删除。

乐观锁定与悲观锁定

悲观锁与读锁一起使用。读取锁将读取时锁定数据对象,从而防止其他并发事务也读取该对象。这样,可以防止发生冲突。

在上面的示例中,Transaction 1将在读取用户数据后将其锁定。尝试读取时,Transaction 2也会阻止进度。一旦Transaction 1完成,Transaction 2就可以进行进度并读取最新状态。这样可以避免冲突,因为事务总是专门处理最新的数据状态。

在写入与读取一样频繁且竞争激烈的情况下,悲观锁定非常有效。

但是,由于悲观锁是排他的,因此并发性降低,从而降低性能。因此,在高并发性和读取比写入更频繁的情况下,检测而不是防止冲突发生的乐观锁定是更可取的。同样,悲观锁定会很快导致死锁。

进一步阅读

1、[1]维基百科:乐观并发控制

2、[2] Stackoverflow:乐观锁定与悲观锁定

卡蒙达的乐观锁

盘古BPM使用乐观锁进行并发控制。如果检测到并发冲突,则会引发异常并将事务回滚。执行UPDATE或DELETE语句时检测到冲突。执行delete或update语句返回受影响的行数。如果此计数等于零,则表示该行先前已更新或删除。在这种情况下,将检测到冲突并OptimisticLockingException引发。

OptimisticLockingException

该OptimisticLockingException可以通过API方法抛出。考虑以下completeTask(...)方法的调用:

taskService.completeTask(aTaskId); // may throw OptimisticLockingException

OptimisticLockingException如果执行方法调用导致并发修改数据,则上述方法可能会抛出异常。

作业执行也可能OptimisticLockingException引发抛出。由于这是预期的,因此将重试执行。

处理乐观锁定异常

如果当前的命令由作业执行器触发,则OptimisticLockingException使用重试自动处理。由于预计会发生此异常,因此它不会减少重试计数。

如果当前命令是由外部API调用触发的,则盘古BPM会将当前事务回滚到最后一个保存点(等待状态)。现在,用户必须决定是否应重试该事务,该如何处理异常。还应考虑,即使交易被回滚,它也可能具有尚未回滚的非交易性副作用。

为了控制事务的范围,可以使用“异步连续性”在活动前后添加显式保存点。

引发乐观锁定异常的常见位置

有一些常见的地方OptimisticLockingException可以扔一个。例如

1、竞争外部请求:同时完成两次相同的任务。

2、流程内的同步点:示例包括并行网关,多实例等。

以下模型显示了OptimisticLockingException可以在其上发生的并行网关。

 

打开并行网关后有两个用户任务。在用户执行任务后,关闭的并行网关将执行合并为一个。在大多数情况下,其中一个用户任务将首先完成。然后执行将在关闭的并行网关上等待,直到第二个用户任务完成。

但是,两个用户任务也可能同时完成。假设上述用户任务已完成。该事务假定他是关闭并行网关上的第一个。下面的用户任务是同时完成的,并且该事务还假定他是关闭并行网关上的第一个用户。这两个事务都尝试更新一行,这表明它们是关闭并行网关上的第一行。在这种情况下,OptimisticLockingException将抛出。其中一个事务已回滚,而另一个成功更新了该行。

乐观锁定和非交易性副作用

发生后OptimisticLockingException,事务将回滚。任何交易工作都将被撤消。诸如文件创建之类的非事务性工作或调用非事务性Web服务的效果将不会被撤消。这可能会以不一致状态结束。

有多种解决方案,最常见的解决方案是使用重试进行最终合并。

内部实施细节

大多数盘古BPM数据库表都包含名为的列REV_。此列代表修订版本。读取一行时,将以给定的“版本”读取数据。修改(UPDATE和DELETE)始终尝试更新当前命令读取的修订。更新会增加修订。执行修改语句后,将检查受影响的行数。如果是计数,1则可以推断出执行修改时所读取的版本仍然是最新的。如果受影响的行数为0,则其他事务在该事务运行时会修改相同的数据。这意味着将检测到并发冲突,并且不允许该事务提交。随后,事务将回滚(或标记为仅回滚),并且OptimisticLockingException 被抛出。

 

 技术支持:盘古BPM工作流平台

相关教程