12

我正在尝试为在不同 JBoss 节点中运行的 cron 作业实现某种信号量。我正在尝试使用数据库(Oracle 11g)作为锁定机制,使用一个表来同步不同节点中的 cron 作业。表很简单:

CREATE TABLE SYNCHRONIZED_CRON_JOB_TASK
(
   ID            NUMBER(10)           NOT NULL,
   CRONJOBTYPE   VARCHAR2(255 Byte),
   CREATIONDATE  TIMESTAMP(6)         NOT NULL,
   RUNNING       NUMBER(1)
);

ALTER TABLE SYNCHRONIZED_CRON_JOB_TASK
   ADD CONSTRAINT PK_SYNCHRONIZED_CRON_JOB_TASK
   PRIMARY KEY (ID); 

因此,当作业启动时,它会在表中搜索其 cronjobtype 的条目,并检查它是否已经在运行。如果不是,它将条目设置运行标志更新为真。第一个选择是通过 JPA CriteriaApi 使用 Hibernate 和悲观锁进行的。

query.setLockMode(javax.persistence.LockModeType.PESSIMISTIC_WRITE);

所有这些操作都在一笔交易中完成。

当一个进程运行时,它进行的查询如下:

[Server:server-two] 10:38:00,049 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,048 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,049 INFO  [stdout] (scheduler-2) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-two] 10:38:00,053 INFO  [stdout] (scheduler-2) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-two] 10:38:00,056 INFO  [stdout] (scheduler-2) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?

此警告没有问题,您可以看到先选择,然后选择更新,因此 Oracle 应该阻止该行上的其他选择操作。但这就是重点,查询没有被阻止,因此可以输入两个作业并毫无问题地进行选择和更新。锁不起作用,如果我们同时运行两个 cron 作业,我们可以看到它:

[Server:server-one] 10:38:00,008 INFO  [stdout] (scheduler-3) 2015-04-30 10:38:00,008 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,008 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,008 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,009 INFO  [stdout] (scheduler-2) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-one] 10:38:00,009 INFO  [stdout] (scheduler-3) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-two] 10:38:00,013 INFO  [stdout] (scheduler-2) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-one] 10:38:00,014 INFO  [stdout] (scheduler-3) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-two] 10:38:00,016 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,015 DEBUG (SynchronizedCronJobService.java:65) - Task read SynchronizedCronJobTask [id=185, type=AlertMailTaskExecutor, creationDate=2015-04-25 07:11:33.0, running=false]
[Server:server-two] 10:38:00,018 INFO  [stdout] (scheduler-2) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?
[Server:server-one] 10:38:00,022 INFO  [stdout] (scheduler-3) 2015-04-30 10:38:00,022 DEBUG (SynchronizedCronJobService.java:65) - Task read SynchronizedCronJobTask [id=185, type=AlertMailTaskExecutor, creationDate=2015-04-25 07:11:33.0, running=false]
[Server:server-one] 10:38:00,024 INFO  [stdout] (scheduler-3) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?

我已尝试在具有两个连接的 SQL 工具 (SQLWorkbenchJ) 上进行此选择以进行更新,并且该工具在此工具中运行良好。但是,如果我在 SQL 工具上选择更新并启动 cron 作业,它们不会被阻塞并且运行时不会出现问题。

我认为问题来自 JPA、Hibernate 或 Oracle 驱动程序,但我不确定。关于问题出在哪里的任何想法?我应该使用 anotehr 策略吗?提前致谢。

4

3 回答 3

4

最后我设法使它工作,但有一些修改。这个想法是使用 LockModeType.PESSIMISTIC_FORCE_INCREMENT 而不是 PESSIMISTIC_WRITE。使用此锁定模式,Cron 作业的行为如下:

  1. 当第一个作业选择更新时,一切都按预期进行,但对象上的版本发生了变化。
  2. 如果另一个作业在第一个作业仍在其事务中时尝试进行相同的选择,JPA 将启动一个 OptimisticLockException,因此如果您捕获该异常,您可以确定它是为读取锁定而引发的。

该解决方案有多种对应物:

  1. SynchronizedCronJobTask 必须有一个版本字段并且受 @Version 的版本控制
  2. 您需要处理 OptimisticLockException,并且应该在事务服务方法之外捕获它,以便在发生解锁时进行回滚。
  3. 恕我直言,这是一个不优雅的解决方案,比 Cron Jobs 等待以前的 Jobs 完成的简单的锁要糟糕得多。
于 2015-04-30T14:13:48.553 回答
1

我可以确认里卡多斯的观察。我用 H2 数据库测试了几种锁定模式,并且都按预期工作。这两种悲观锁定模式都不能与 Oracle 数据库一起正常工作。我没有尝试乐观锁定,但令人惊讶的是,有一种锁定模式根本不适用于顶级狗。

于 2018-07-31T16:31:50.023 回答
0

将锁定模式设置为 PESSIMISTIC_READ,因为您需要第二台服务器在更改数据之前知道第一台服务器的更改。

于 2015-04-30T10:11:42.657 回答