Spring Batch高级特性与实践详解
定位:Batch 第 02 篇,讲透重启续跑机制、跳过与重试策略、并行处理模式、监听器与生产工程实践
适用版本:Spring Batch 5.x(Spring Boot 3.x)
目录
一、重启与续跑
1.1 机制
执行失败 → 元数据记录了执行到哪个 Step、哪个 Chunk
重启(用同一 JobInstance)→ 从失败的 Step 继续
已成功的 Chunk 不重复(已提交),从失败位置往后跑
1.2 条件与限制
| 要点 | 说明 |
|---|---|
| Step 可重启 | 需要记录位置(Reader 可定位,如数据库按 id 继续) |
| 已完成 Step 不重跑 | 默认跳过已 COMPLETED 的 Step |
| 强制重跑 | allowStartIfComplete(true) 可让已完成 Step 重新执行 |
| 不可恢复的场景 | 流式读无位置记录、副作用已外部发生------重跑会重复 |
1.3 重启的前提是幂等
重启意味着部分数据会被重新处理------写操作必须幂等(唯一键去重/状态机),否则重跑产生重复数据。这是批处理设计的第一原则。
二、跳过与重试
2.1 Skip(跳过)
指定某类异常时跳过该条,继续处理后续数据(限制跳过总数):
.faultTolerant()
.skip(ValidationException.class)
.skipLimit(100)
用途:脏数据/校验不通过的条目不阻塞整个批处理;超过 skipLimit 才失败。
2.2 Retry(重试)
指定某类异常时重试该条(限制次数):
.retry(DeadlockLoserDataAccessException.class)
.retryLimit(3)
用途:瞬态故障(死锁、网络抖动)自动重试。
2.3 记录与监听
SkipListener:onSkipInRead/onSkipInProcess/onSkipInWrite ------ 记录被跳过的数据
纪律:跳过的数据必须可追溯(落日志/落表),否则"数据少了"无法排查。
三、并行处理
3.1 四种模式
| 模式 | 做法 | 注意 |
|---|---|---|
| 多线程 Step | 单 Step 内多线程并行处理(.throttleLimit/线程池) |
Reader/Processor/Writer 必须线程安全 |
| 并行 Step | 互不依赖的 Step 用 Flow 同时执行 | 步骤间无数据依赖 |
| 分区(Partitioning) | 把大任务按范围拆成子分区,各分区独立并行 | 分区键要可拆分(如按 id 段) |
| 远程分块 | 读在主节点,处理分发到多机 | 跨进程,架构更复杂 |
3.2 多线程的线程安全要求
并行处理时:
Reader 被多线程调用 → 必须支持并发读或加锁
Processor 无状态
Writer 批量写要支持并发提交
不满足 → 数据错乱/丢失
3.3 分区示例思想
数据范围 1~1000 万
→ 拆成 10 个分区(1~100万、100万~200万...)
→ 每分区一个独立的处理实例并行跑
→ 汇总等待全部分区完成
适合数据可按键自然切分、各分区无交互的场景。
四、监听器与可观测
4.1 监听器体系
| 监听器 | 时机 |
|---|---|
JobExecutionListener |
Job 开始/结束 |
StepExecutionListener |
Step 开始/结束 |
ChunkListener |
每个 Chunk 前后 |
ItemReadListener 等 |
单条读/处理/写前后与异常 |
4.2 用途
① 日志:记录执行开始/结束、耗时、读写计数
② 指标:对接监控(吞吐、失败率、时长)
③ 通知:成功/失败发告警
④ 资源准备/清理:开始前准备、结束后清理
执行元数据(读写计数、提交数、状态)本身已入库,是监控的数据源。
五、工程实践
5.1 幂等设计(最重要)
批处理必然重跑(失败重启/补数),写操作必须幂等:
① 唯一键:业务唯一索引,重复插入被拒
② 状态机:已处理状态不再重复处理
③ 去重表/去重键:处理前检查
5.2 调度与编排
生产用外部调度平台(而非应用内 @Scheduled):
依赖编排(A 完成后跑 B)、失败告警、手动重跑、执行日历
5.3 资源隔离
批处理对库的压力大:
① 错峰:避开在线高峰
② 限流:控制并发与批量大小,避免拖垮在线库
③ 读从库/独立库:大批量读走从库或专用库
5.4 元数据治理
执行历史持续增长 → 定期清理(保留期策略)
关键执行结果归档审计
六、总结
- 重启续跑:基于元数据从失败位置继续;前提是 Reader 可定位 + 写操作幂等;已完成 Step 默认不重跑。
- 容错:Skip 跳脏数据(限数量 + 必须记录)、Retry 抗瞬态故障(限次数)。
- 并行:多线程 Step(线程安全要求)、并行 Step(Flow)、分区(按键拆分)、远程分块;按数据可拆分性选择。
- 监听器:Job/Step/Chunk/Item 各级钩子,支撑日志、指标、告警;执行元数据是监控数据源。
- 工程纪律:幂等第一、调度平台化、资源隔离防拖垮在线、元数据定期清理。
七、常见高频面试题
1. Spring Batch 如何实现失败后从断点续跑?
要点:基于执行元数据。每个 Chunk 是提交点,执行状态(哪个 Step、哪个 Chunk、读到哪)持久化在元数据表;失败后用同一 JobInstance 重启,框架跳过已成功的部分,从失败的 Step/Chunk 继续。前提:① Step 可重启------Reader 能定位继续位置(如按主键范围);② 写操作幂等------重跑不产生重复。限制:已完成的 Step 默认不重跑(需 allowStartIfComplete 强制)。
2. 为什么批处理必须做幂等?
要点:重跑是批处理的常态------失败重启、人工补数、重复触发都可能让部分数据被再次处理。若写操作不幂等,重跑产生重复数据(重复结算、重复发通知,资损风险)。实现:① 业务唯一键(唯一索引拒绝重复插入);② 状态机(已处理状态跳过);③ 去重表/幂等键检查。幂等是批处理可靠性的第一设计原则,与在线接口的幂等要求同源。
3. Skip 和 Retry 策略怎么用?要注意什么?
要点:Skip------指定某类异常(如校验失败)跳过该条继续处理,配 skipLimit 限制跳过总数,超限才失败;用于脏数据不阻塞批处理。Retry------指定瞬态异常(死锁、网络抖动)重试该条,配 retryLimit;用于抗瞬时故障。注意:① 被跳过的数据必须记录(SkipListener 落日志/表),否则数据缺失无法排查;② 重试的写操作要幂等(重试可能已部分生效);③ 谨慎定义跳过异常类型,别把严重错误也跳过。
4. Spring Batch 有哪些并行处理方式?
要点:四种。① 多线程 Step------单 Step 内多线程并行处理,要求 Reader/Processor/Writer 线程安全;② 并行 Step------用 Flow 让互不依赖的 Step 同时执行;③ 分区(Partitioning)------把大数据按键拆成子分区,各分区独立并行处理后汇总,适合可自然切分的数据;④ 远程分块------读在主节点、处理分发到多台机器(跨进程)。选择:单机内优先多线程/分区,步骤独立用并行 Step,超大规模跨机才用远程分块。
5. 多线程 Step 的线程安全要注意什么?
要点:并行处理时组件被多线程并发调用。Reader------必须支持并发读(线程安全的读取或内部加锁),否则重复读/漏读;Processor------保持无状态,不共享可变状态;Writer------批量写要支持并发提交,数据库操作注意连接与事务边界(每个线程各自的提交)。另外共享的聚合统计要线程安全。不满足会导致数据错乱、丢失、重复------并行化前必须逐一确认线程安全性。
6. 分区(Partition)是怎么工作的?
要点:把大任务按某个键(通常是主键范围或哈希)拆成多个互不重叠的子分区,每个分区分配给一个独立的工作线程/实例并行处理(各自有自己的 Reader 范围),全部完成后汇总。关键:① 分区逻辑要保证不重叠不遗漏;② 各分区数据独立、无交叉依赖;③ 分区数量决定并行度。适合百万~亿级可按键切分的数据。与多线程 Step 的区别:分区是数据切分后各自独立流,多线程是同一流程并发执行。
7. 批处理任务怎么做监控告警?
要点:数据源是执行元数据(状态、读写计数、耗时)+ 监听器钩子。建设:① 执行结果监控------成功/失败、执行时长(超阈值告警)、未按时触发告警;② 数据处理量监控------读写计数异常(为 0 或骤变告警);③ 跳过/失败明细------SkipListener 记录可查;④ 依赖与资源------数据源连接、磁盘(文件型任务)。落地:监听器上报指标到监控平台,调度平台提供失败告警与执行日历。批处理无人值守,监控必须完备。
8. 批处理和在线服务共用数据库,怎么防止互相影响?
要点:批处理大数据量读写会与在线请求争抢数据库资源(连接、IO、锁)。措施:① 时间错峰------批处理安排在在线低峰;② 限流------控制批处理的并发度与批量大小,避免打满连接池;③ 读写分离------批处理的大批量读走从库或专用只读实例,写控制速率;④ 独立连接池------批处理与在线分池,互不抢占;⑤ 必要时独立库/资源组隔离。原则:批处理是"可延时的",在线是"必须保障的",资源分配向在线倾斜。
9. 如何防止批处理任务被并发重复触发?
要点:多层防护。① 框架层------同一 JobInstance(同参数)已在运行/完成时,再次启动被拒绝(元数据表实例唯一性约束);② 调度层------调度平台配置互斥执行(上一次未完成不触发下一次);③ 应用层------分布式场景加分布式锁(防多实例同时触发);④ 参数设计------用唯一参数标识每次运行,避免歧义。核心是"同一逻辑运行只执行一次",与在线接口的幂等防重思想一致。
10. 什么时候该用 Spring Batch,什么时候直接用消息队列?
要点:按数据形态与时效。Spring Batch 适合:有明确数据集(文件/表)、批量加工、需要进度跟踪与断点续跑、离线窗口执行------强调"处理完这批数据"。消息队列适合:持续流入的事件、实时/准实时处理、削峰、解耦------强调"来一条处理一条"。也可组合:队列积压的事件攒成批,用批处理框架定时消费(如 Spark/批任务拉取)。选择标准:批量边界清晰、需重跑与进度管理用 Batch;流式、低延迟用消息。
