我们有两个 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)
没有任何副本执行该作业
在我们的情况下您会如何进行?
我们的框架和数据库版本:
根据https://github.com/spring-projects/spring-boot/issues/28802的建议,我们设置了
spring.batch.jdbc.isolation-level-for-create=SERIALIZABLE
,但现在我们有不同的不良行为:
CannotSerializeTransactionException
(作业本身在数据库中不执行任何操作),并且它们都不执行它我们构建了一个有效的解决方法(在“执行日期”列上具有 UniqueConstraint 的数据库表)。
但实际上,我们真的很想使用 Spring Batch 作业存储库作为并发作业执行的锁。
使用这种方法我们遇到了以下问题:
副本 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 隔离级别