多应用实例同时运行同一Spring Batch作业出现事务异常如何解决
Spring Batch多实例并发执行报错解决方案
报错根因
你遇到的PostgreSQL序列化事务错误,是多实例并发向Spring Batch共享元数据表插入JobExecution记录时,读写冲突导致的。两个核心问题导致你的现有配置没有生效:
- YAML配置错误:当前配置的缩进、键名都不符合Spring Boot规范,
isolation-level-for-create配置未生效,默认的事务隔离级别无法应对多实例并发写入场景 - 自定义监听器执行时机过晚:
beforeJob是JobExecution已经创建完成、元数据写入事务提交后才触发的,无法提前拦截并发创建请求,只能在冲突发生后终止多余作业,不能避免事务异常
方案1:仅允许任意一个实例运行该作业(适合定时调度场景)
该方案从源头避免并发创建作业的请求,完全解决冲突问题:
- 首先修正Spring Batch配置,正确配置事务隔离级别:
spring: batch: job: enabled: false initialize-schema: always repository: # 配置作业创建时的事务隔离级别为序列化,解决元数据写入冲突 isolation-level-for-create: ISOLATION_SERIALIZABLE
- 添加分布式锁控制作业启动,推荐使用轻量的ShedLock组件:
- 引入ShedLock的JDBC和Spring集成依赖,配置ShedLock使用当前共享的PostgreSQL数据库作为锁存储
- 在触发作业执行的调度方法上添加锁注解:
@SchedulerLock(name = "你的作业唯一名称", lockAtMostFor = "30m", lockAtLeastFor = "5m") public void scheduledLaunchJob() throws JobExecutionException { // 原有作业启动逻辑 JobParameters params = new JobParametersBuilder() .addString("JobID", UUID.randomUUID().toString(), true) .toJobParameters(); jobLauncher.run(job, params); }
该注解会保证同一时间只有一个实例能进入方法启动作业,完全避免并发创建请求。
如果不想引入额外依赖,可以把作业启动逻辑包裹在SERIALIZABLE隔离级别的事务中,启动前先查询是否有运行中的同名作业:
@Transactional(isolation = Isolation.SERIALIZABLE) public void launchJobIfNotRunning(Job job) throws JobExecutionException { Set<JobExecution> runningExecutions = jobExplorer.findRunningJobExecutions(job.getName()); if (runningExecutions.isEmpty()) { JobParameters params = new JobParametersBuilder() .addString("JobID", UUID.randomUUID().toString(), true) .toJobParameters(); jobLauncher.run(job, params); } }
方案2:支持多实例同时运行同一作业(适合大任务分片处理场景)
如果需要多实例同时执行同一个作业的不同分片,按以下配置即可:
- 首先修正上述的事务隔离级别配置,解决元数据写入的事务冲突问题
- 采用Spring Batch官方的远程分区/远程分块模式:
- 配置单个管理节点负责拆分作业为多个独立分片
- 多个工作节点从共享元数据表拉取分片执行,Spring Batch会自动分配不同分片给不同实例,不会出现执行冲突
- 如果你只是需要同一个作业的不同参数实例同时在多节点运行,只需要保证每次启动的
JobParameters唯一(你当前已经用UUID作为JobID,符合要求),再修正隔离级别配置即可解决报错,不需要额外改造。
内容的提问来源于stack exchange,提问作者Viraj Dhamal
相关产品推荐
相关产品推荐

