如何确保 Spring Batch 作业实例在两个(或多个)节点上同时启动时恰好执行一次

问题描述 投票:0回答:1

我们有两个 Spring Boot 后端副本在两个节点上运行。 两个后端同时启动同一个作业实例。作业实例使用参数“执行日期”来标识,该参数是“四舍五入到分钟开始”的 Java 日期,即秒、毫秒设置为零。执行作业实例的代码:

try {
    var executionDate = DateTimeUtils.roundDownToBeginOfMinute(new Date());
    var params = new HashMap<String, JobParameter>();
    params.put("execution-date", new JobParameter(executionDate, true));

    var jobExecution = jobLauncher.run(job, new JobParameters(params));
    ...

} catch (JobInstanceAlreadyCompleteException | JobExecutionAlreadyRunningException e) {
    log.info("Job already executed on other node");
} catch (Exception e) {
    log.error("Unexpected exception", e);
}

使用这种方法我们遇到了以下问题:

  • 副本 1 获得 org.springframework.batch.core.repository.JobExecutionAlreadyRunningException,如预期

  • 副本2,327ms后,报告:org.springframework.dao.DuplicateKeyException:PreparedStatementCallback; SQL [插入 BATCH_JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION) 值 (?, ?, ?, ?)]; ORA-00001:违反了唯一约束(APP.JOB_INST_UN)

  • 没有任何副本执行该作业

在我们的情况下您会如何进行?

我们的框架和数据库版本:

  • 春季启动:2.7.15
  • 弹簧核心:5.3.29
  • spring-batch-core:4.3.9
  • DB:Oracle Database 19c 企业版版本 19.0.0.0.0 - 生产版本 19.19.0.0.0,区分大小写:plain=upper,delimited=exact

根据https://github.com/spring-projects/spring-boot/issues/28802的建议,我们设置了

spring.batch.jdbc.isolation-level-for-create=SERIALIZABLE
,但现在我们有不同的不良行为:

  • 在某些情况下,两个副本在尝试启动作业时都会得到
    CannotSerializeTransactionException
    (作业本身在数据库中不执行任何操作),并且它们都不执行它
  • 在一种情况下,我们执行了两次相同的作业实例,我们在作业存储库中看到两个具有相同作业参数的条目(表 BATCH_JOB_EXECUTION、BATCH_JOB_EXECUTION_PARAMS)

我们构建了一个有效的解决方法(在“执行日期”列上具有 UniqueConstraint 的数据库表)。

但实际上,我们真的很想使用 Spring Batch 作业存储库作为并发作业执行的锁。

spring-boot spring-batch
1个回答
0
投票

使用这种方法我们遇到了以下问题:

副本 1 得到 org.springframework.batch.core.repository.JobExecutionAlreadyRunningException,如预期 副本 2,327 毫秒后,报告:org.springframework.dao.DuplicateKeyException:PreparedStatementCallback; SQL [插入 BATCH_JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION) 值 (?, ?, ?, ?)]; ORA-00001: 违反唯一约束 (APP.JOB_INST_UN) 没有一个副本执行该作业 如果您遇到我们的情况,您会如何处理?

事实不应该如此。使用

SERIALIZABLE
隔离级别是可行的方法,这似乎已经解决了您所描述的问题。

我们设置了 spring.batch.jdbc.isolation-level-for-create=SERIALIZABLE,但现在我们有不同的不良行为:

在某些情况下,两个副本在尝试启动作业时都会出现 CannotSerializeTransactionException(作业本身在数据库中不执行任何操作),并且没有一个副本执行它

在一种情况下,我们执行了两次相同的作业实例,我们在作业存储库中看到两个具有相同作业参数的条目(表 BATCH_JOB_EXECUTION、BATCH_JOB_EXECUTION_PARAMS)

您可能需要增加

INITRANS
参数来解决这些问题,请参阅 Spring Batch ORA-08177:运行单个作业时无法序列化此事务的访问,SERIALIZED 隔离级别

© www.soinside.com 2019 - 2024. All rights reserved.