Spring Batch中的Step:批处理流水线的"车间主任"是如何炼成的? 🏭
副标题:从读数据到写结果,Step的十八般武艺全解析
一、Step是谁?------批处理的"车间主任"
如果说Job是批处理项目的"总指挥",那Step就是流水线上的"车间主任"------负责具体工序的执行,比如数据读取、加工、写入。每个Step都是Job的"左膀右臂",可以串行执行(一个接一个),也可以并行处理(多线程加速),甚至搞点"花活"(比如条件跳转)。
举个栗子:
- Step1 :从CSV文件读取订单数据(
ItemReader
)。 - Step2 :校验订单金额是否合法(
ItemProcessor
)。 - Step3 :把合法订单写入数据库(
ItemWriter
)。
效果:流水线作业,数据像快递包裹一样,一站接一站被处理。
二、Step的用法------如何让车间主任高效搬砖?
1. 基础三件套:Reader + Processor + Writer
java
@Bean
public Step processOrderStep(StepBuilderFactory steps) {
return steps.get("processOrderStep")
.<Order, ValidOrder>chunk(100) // 每攒100条提交一次
.reader(orderReader()) // 读数据:从CSV、DB、API等
.processor(orderValidator()) // 加工数据:校验、转换、过滤
.writer(orderWriter()) // 写结果:存DB、发消息队列、写文件
.faultTolerant() // 开启容错
.skipLimit(10) // 最多跳过10条脏数据
.skip(InvalidOrderException.class)
.retryLimit(3) // 失败重试3次
.retry(NetworkTimeoutException.class)
.build();
}
关键配置:
- chunk(size):事务边界,攒够size条数据才提交,减少IO压力。
- faultTolerant():容错机制,支持跳过(Skip)、重试(Retry)。
- skip与retry:精准控制哪些异常要跳过或重试,避免"一崩全崩"。
2. 多Step协作:顺序、条件、并行
- 顺序执行:Step1 → Step2 → Step3(默认)。
- 条件跳转:根据Step1的结果,决定执行Step2还是Step3。
java
// 根据Step1的结果,动态决定下一步
public Job conditionalJob() {
return jobs.get("conditionalJob")
.start(step1())
.on("FAILED").to(step2()) // 如果Step1返回"FAILED",执行Step2
.from(step1()).on("*").to(step3()) // 其他情况执行Step3
.end()
.build();
}
- 并行执行:多个Step同时跑,加速处理。
java
// Step1和Step2并行执行
public Job parallelJob() {
Flow flow1 = new FlowBuilder<Flow>("flow1").start(step1()).build();
Flow flow2 = new FlowBuilder<Flow>("flow2").start(step2()).build();
return jobs.get("parallelJob")
.start(flow1)
.split(new SimpleAsyncTaskExecutor()) // 异步执行
.add(flow2)
.build()
.build();
}
三、原理------Step的"内功心法"
1. Step的核心组件
组件 | 职责 | 比喻 |
---|---|---|
ItemReader | 读取数据(文件、数据库、API等) | 流水线的"上料机器人" |
ItemProcessor | 处理数据(校验、转换、过滤) | 质检员+加工师傅 |
ItemWriter | 写入结果(数据库、文件、消息队列等) | 打包发货的物流小哥 |
Tasklet | 自定义逻辑(比如清理临时文件) | 杂活全能选手 |
2. Step执行流程
- 读数据:ItemReader逐条读取数据,直到攒够Chunk大小(比如100条)。
- 处理数据:ItemProcessor对每条数据加工,可返回null过滤掉无效数据。
- 写结果:ItemWriter一次性写入Chunk中的所有数据。
- 提交事务:整个Chunk处理成功后提交,失败则回滚。
比喻 :
Step像一个智能流水线,每攒够一箱(Chunk)包裹,先质检(Processor),再打包(Writer),最后统一发货(事务提交)。
四、避坑指南------Step的"翻车现场"
1. Chunk大小不合理
- 问题:Chunk太大→内存溢出;Chunk太小→事务开销高。
- 解决:根据数据量和内存调整,一般100~1000条,像吃薯片------别一口塞一包。
2. Processor中吞了异常
- 坑点:在Processor里try-catch却不抛出异常,Spring Batch以为数据正常,导致脏数据入库。
- 忠告:要么处理异常,要么抛出,别当"哑巴"!
3. Reader未正确重置状态
- 问题 :自定义ItemReader未实现
ItemStream
接口,重启Job时无法恢复读取位置。 - 解决 :让Reader实现
ItemStream
,并在open()
中恢复状态。
五、最佳实践------老司机的经验之谈
1. 读写分离
- Reader :用分页查询(
JdbcPagingItemReader
)避免一次性加载百万数据。 - Writer :用批量写入(
JdbcBatchItemWriter
)减少数据库压力。
2. 性能优化三板斧
- 多线程Step :用
TaskExecutor
让一个Step内部多线程处理Chunk。
java
// 多线程Step
@Bean
public Step multiThreadStep() {
return steps.get("multiThreadStep")
.<Order, ValidOrder>chunk(100)
.reader(reader())
.processor(processor())
.writer(writer())
.taskExecutor(new SimpleAsyncTaskExecutor()) // 开启多线程
.throttleLimit(4) // 最大并发线程数
.build();
}
- 分区处理(Partitioning):将数据分片,多个线程或节点并行处理。
- 异步Step:多个Step并行执行(见前文代码示例)。
3. 监控与日志
- Listener :通过
StepExecutionListener
记录耗时、数据量、错误信息。
java
public class LoggingStepListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
System.out.println("Step开始:我要发车了!🚗");
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
System.out.println("Step结束:处理了" + stepExecution.getWriteCount() + "条数据,耗时"
+ stepExecution.getEndTime().getTime() - stepExecution.getStartTime().getTime() + "ms");
return stepExecution.getExitStatus();
}
}
六、面试考点------如何让面试官直呼内行?
1. 问题:Step的Chunk机制如何保证数据一致性?
答案 :
每个Chunk处理完成后统一提交事务,若中途失败,整个Chunk回滚,避免"半成品"数据。
2. 问题:如何实现Step的断点续传?
答案 :
通过JobRepository
记录StepExecution状态,重启时根据JobInstance
和JobParameters
定位上一次执行位置,ItemReader需实现ItemStream
以支持状态恢复。
3. 问题:Step的Processor返回null会发生什么?
答案 :
该条数据会被过滤,不会进入Writer,适合用于数据清洗场景。
七、总结------Step的终极奥义
Spring Batch的Step,是批处理流水线的核心车间主任------分工明确、稳如泰山、灵活多变。掌握它的读、处理、写三件套,合理配置Chunk和容错机制,再结合多线程与分区处理,你的批处理任务将如虎添翼!
记住三点:
- 事务边界:Chunk大小是性能与安全的平衡点。
- 异常处理:Skip和Retry是避免"全军覆没"的关键。
- 监控先行:Listener和日志是排查问题的"火眼金睛"。