Skip to content

Spring Batch 步骤重启配置指南

🚀 让任务中断后智能恢复执行,避免重复处理和数据混乱


🔍 引言:为什么需要配置步骤重启

在 Spring Batch 中,任务(Job)重启会直接影响步骤(Step)的执行逻辑。合理配置重启策略能解决以下核心问题:
防止资源冲突:某些步骤只能执行一次(如初始化操作)
确保数据完整性:失败后从断点继续处理而非重新开始
灵活控制流程:不同步骤可设置独立的重启策略

NOTE

本指南基于 Kotlin DSL 实现,避免 XML 配置,采用注解和类型安全配置


⚙️ 一、设置步骤启动限制(Start Limit)

控制步骤的最大执行次数,避免资源被重复初始化。

典型场景

  • 步骤会锁定或修改关键资源(如数据库表结构变更)
  • 步骤失败后需人工介入才能再次执行

Kotlin 配置示例

kotlin
@Configuration
class BatchConfig {

    @Bean
    fun step1(jobRepository: JobRepository, transactionManager: PlatformTransactionManager): Step {
        return StepBuilder("step1", jobRepository)
            .chunk<String, String>(10, transactionManager)
            .reader(itemReader())
            .writer(itemWriter())
            .startLimit(1)  // 关键配置:仅允许执行1次
            .build()
    }
}

WARNING

当超过 startLimit 限制时,会抛出 StartLimitExceededException。默认值为 Int.MAX_VALUE(无限次)


🔄 二、重启已完成的步骤(Allow Restart)

强制已完成的步骤在任务重启时再次执行,适用于需重复操作的场景。

典型场景

  • 清理临时资源的步骤(如删除处理完的文件)
  • 数据校验步骤(每次任务启动都需执行)

Kotlin 配置示例

kotlin
@Bean
fun cleanupStep(jobRepository: JobRepository, transactionManager: PlatformTransactionManager): Step {
    return StepBuilder("cleanupStep", jobRepository)
        .chunk<File, File>(5, transactionManager)
        .reader(fileReader())
        .writer(fileDeleter())
        .allowStartIfComplete(true)  // 关键配置:总是执行
        .build()
}

🏈 三、综合示例:足球数据统计任务

通过真实场景演示重启策略的组合应用。

业务需求

  1. playerLoad:加载球员数据 → 仅执行1次
  2. gameLoad:加载比赛数据 → 每次重启都执行
  3. playerSummarization:统计球员数据 → 最多执行2次

Kotlin 完整配置

kotlin
@Configuration
class FootballJobConfig {

    @Bean
    fun footballJob(jobRepository: JobRepository,
                   playerLoad: Step,
                   gameLoad: Step,
                   playerSummarization: Step): Job {
        return JobBuilder("footballJob", jobRepository)
            .start(playerLoad)
            .next(gameLoad)
            .next(playerSummarization)
            .build()
    }

    // 步骤1:球员数据加载(仅运行1次)
    @Bean
    fun playerLoad(jobRepository: JobRepository, txManager: PlatformTransactionManager): Step {
        return StepBuilder("playerLoad", jobRepository)
            .chunk<Player, Player>(100, txManager)
            .reader(playerFileReader())
            .writer(playerDBWriter())
            .build() // 默认不重启
    }

    // 步骤2:比赛数据加载(总是运行)
    @Bean
    fun gameLoad(jobRepository: JobRepository, txManager: PlatformTransactionManager): Step {
        return StepBuilder("gameLoad", jobRepository)
            .chunk<Game, Game>(50, txManager)
            .reader(gameFileReader())
            .writer(gameDBWriter())
            .allowStartIfComplete(true) // 关键配置
            .build()
    }

    // 步骤3:数据统计(最多运行2次)
    @Bean
    fun playerSummarization(jobRepository: JobRepository, txManager: PlatformTransactionManager): Step {
        return StepBuilder("playerSummarization", jobRepository)
            .chunk<PlayerStats, Summary>(20, txManager)
            .processor(statsProcessor())
            .writer(summaryWriter())
            .startLimit(2) // 关键配置
            .build()
    }
}

📊 四、执行流程分析

通过时序图展示三次任务执行过程:

关键行为说明:

  1. 首次执行

    • playerLoad 成功加载 400 名球员
    • gameLoad 处理 11 个比赛文件
    • playerSummarization 5 分钟后失败
  2. 第一次重启

    • playerLoad 被跳过(已完成且未配置重启)
    • gameLoad 处理新增的 2 个比赛文件
    • playerSummarization 30 分钟后再次失败
  3. 第二次重启

    • playerSummarization 被阻止执行(超过 startLimit=2)
    • 需要人工介入或创建新 JobInstance

💡 最佳实践建议

  1. 关键原则

  2. 配置选择指南

    步骤类型startLimitallowStartIfComplete
    数据库初始化1false
    临时文件清理Int.MAX_VALUE✅ true
    核心数据处理2-5false
    低优先级任务Int.MAX_VALUEfalse
  3. 故障排查技巧

    kotlin
    // 在监听器中捕获重启异常
    class RestartListener : StepExecutionListener {
        override fun beforeStep(stepExecution: StepExecution) {
            if (stepExecution.startCount > 1) {  
                logger.warn("步骤多次重启:${stepExecution.stepName}")
            }
        }
    }

CAUTION

避免将 startLimit 设置为 0,这会导致步骤永远无法执行!


🚨 常见问题解决

问题:步骤意外跳过
解决:检查是否同时满足

  • 上次执行状态为 COMPLETED
  • allowStartIfComplete=false(默认值)

问题StartLimitExceededException
解决

  1. 通过 JobOperator 创建新 JobInstance
  2. 在代码中动态调整限制:
    kotlin
    fun flexibleStep(): Step {
        val limit = if (isProduction) 3 else 10
        return StepBuilder("flexStep", jobRepository)
            .startLimit(limit)
            // ...
    }

通过合理配置步骤重启策略,您的批处理任务将获得:✨ 更强的容错能力 + 🛡️ 数据一致性保障 + ⚡️ 资源利用率优化