【问题标题】:Limit concurrent execution of an application process using database使用数据库限制应用程序进程的并发执行
【发布时间】:2015-07-07 13:04:33
【问题描述】:

我被要求实施“访问策略”,以限制直接连接到数据库的应用程序(不是 Web 应用程序)中某个进程的并发执行量。 p>

应用程序在多台机器上运行,如果多个用户尝试调用该进程,则在给定时间只允许执行一次,另一个必须返回错误消息(不等待第一次执行结束)。

虽然我使用的是 Java/Postgres,但这是一个普遍的问题。

鉴于我在多台机器上运行相同的应用程序,我能想到的最简单的解决方案是实现某种“数据库标志”。

类似于检查进程当前是否处于活动状态:

SELECT Active FROM Process

如果它处于活动状态,则返回“并发访问策略错误”。如果没有,请激活它:

UPDATE Process SET Active = 'Y'

执行完成后,只需更新活动标志:

UPDATE Process SET Active = 'N'

但是,我遇到了一个大问题:

  • 如果我不使用数据库事务来更改活动标志,并且应用程序被终止,活动标志将永远保留 Y 值。
  • 如果我使用数据库事务,第一点就解决了。但是,主机中活动标志的更改(从 N 到 Y)仅在提交后可见,因此其他主机永远不会读取带有 Y 值的活动,因此无论如何都会执行。

有什么想法吗?

【问题讨论】:

  • 只是一些初步想法:您可以在单独的事务中设置标志(可能与显式锁定结合使用),或者您可以查看例如hazelcast 用于某种集群范围的锁(尽管这对您的应用程序来说可能有点过分)。
  • 与问题没有直接关系,但您可以从使用INSERT 而不是UPDATE 中受益。当第一个用户调用该进程时,应在数据库中插入一个新行。当第二个用户调用该进程时,他的INSERT 使用相同的主键应该会失败。

标签: java database postgresql concurrency


【解决方案1】:

不要打扰活动标志,而只需根据用户 ID 锁定一行。将该行锁定在专用事务/连接中。当其他用户尝试锁定该行(使用 SELECT ... FOR UPDATE)时,您会收到一个错误,您可以报告它。

如果持有事务的进程失败,则释放锁。如果它退出,则释放锁。如果数据库重新启动,锁就会被释放。

全面取胜。

【讨论】:

  • 是的!绝对是最简单和最直接的解决方案。完美运行。为了完成该解决方案,我必须将 NOWAIT 选项与 SELECT ... FOR UPDATE 一起使用,以便在第二个进程尝试获取锁时获得异常。非常感谢!!
【解决方案2】:

不要只有一个简单的 Y/N 标志,而是设置活动的时间戳,并让您的客户端应用程序定期设置它(比如每分钟或每五分钟)。然后,如果一个客户端崩溃,其他客户端将不得不等待超过该时间限制,然后假设客户端已死并接管。这只是某种“心跳”机制,用于检查启动进程的客户端是否还活着。

更简单的解决方案是将数据库配置为一次只接受一个连接?

【讨论】:

    【解决方案3】:

    我不确定 RDBMS 是否是解决此类问题的最佳系统。但我最近在 SQL Server 2012 中实现了类似的东西。所以这就是我从那次经历中学到的东西。

    一般来说,我的意思是原则上,您需要一个原子操作“检查值,更新(单个记录的)值”,即原子SELECT/UPDATE。这使事情变得复杂。而且因为在 RDBMS 中通常没有这种标准的单原子操作,所以您可以熟悉并使用ISOLATION LEVEL SERIALIZABLE

    这就是我在 SQL Server 2012 中实现它的方式,并且我已经认真测试过,它运行良好。我有一个名为DistributedLock 的表,其中的每条记录都代表一个逻辑锁。我允许的操作是tryLockreleaseLock(它们被实现为两个存储过程)。 tryLock 是非阻塞的(实际上是非阻塞的)。如果成功,它会返回一些 ID/stamp 给调用者,调用者稍后可以使用该 ID/stamp 调用releaseLock。如果调用releaseLock而实际上没有持有锁(没有最新的ID/stamp),调用成功并且什么都不做,否则(如果调用者有锁)调用成功并释放调用者持有的锁.我也支持超时。因此,如果某个进程获取了给定锁/记录的 ID/戳记,却忘记释放它,它会在一段时间后自动过期。

    这是表格的样子。

    [DistributedLockID] [bigint] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL -- surrogate PK 
    [ResourceID] [nvarchar](256) NOT NULL -- resource/lock logical identifier 
    [Duration] [int] NOT NULL 
    [AcquisitionTime] [datetime] NULL 
    [RecordStamp] [bigint] NOT NULL 
    

    我想你可以弄清楚其余的(或者尝试一下,如果你卡住了,然后 ping 我)。

    【讨论】:

    • +1 感谢您的详细解释。我只是将 Will Hartung 标记为正确答案,因为它是一个非常简单但有用的 Postgres 解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-30
    • 1970-01-01
    • 2011-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多