Chunk 处理模型详解
本章定位:彻底理解 Spring Batch 的核心执行模型——Chunk 循环,掌握事务边界、提交频率优化和 ChunkListener。
定义与作用
Chunk(块) 是 Spring Batch 中 Step 执行的基本事务单元。一个 Chunk Step 的循环是:读 N 条 → 逐条处理 → 一次性写 N 条 → 提交事务。这个过程反复执行直到数据读完。
飞翔科技架构师白歌的解释:
"Chunk 就像搬家的纸箱。搬家工人(Reader)一件一件(read)把家具从旧房子搬出来,放进箱子。箱子满了(Chunk Size 达标),一辆卡车(Writer)一次性把整箱家具(write)运到新房子。运到后签字确认(COMMIT)。"
核心原理
Chunk 循环的完整流程
事务边界
┌─────────────────── Chunk 事务 ───────────────────┐
│ ItemReader.read() × 10 │
│ ItemProcessor.process() × 10 │
│ ItemWriter.write(chunk) │
│ ──────────────────────────────────────────────── │
│ COMMIT │
└──────────────────────────────────────────────────┘
关键规则:
| 规则 | 说明 |
|---|---|
| 一个 Chunk = 一个事务 | Reader/Processor/Writer 共享同一事务 |
| commit-interval = chunk-size | 框架每 chunk-size 条提交一次 |
| 最后不足 chunk-size 的剩余数据 | 也会作为一个 Chunk 提交 |
| Writer 失败 | 整个 Chunk 回滚,Reader 指针回到 Chunk 起始位置 |
| Chunk 提交后 | ExecutionContext 立即持久化 |
完整示例
场景一:飞翔科技——Chunk Size 调优
@Bean
public Step tunedImportStep(JobRepository jobRepository,
PlatformTransactionManager tx,
ItemReader<Order> reader,
ItemProcessor<Order, OrderEntity> processor,
ItemWriter<OrderEntity> writer) {
return new StepBuilder("tunedImportStep", jobRepository)
.<Order, OrderEntity>chunk(500, tx) // Chunk Size=500
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
Chunk Size 影响分析:
| Chunk Size | 事务数(10 万条) | 事务开销 | 回滚粒度 | 内存占用 |
|---|---|---|---|---|
| 10 | 10000 | 极高 | 细(10 条) | 低 |
| 100 | 1000 | 中 | 中(100 条) | 低 |
| 500 | 200 | 低 | 粗(500 条) | 中 |
| 1000 | 100 | 极低 | 很粗(1000 条) | 较高 |
场景二:ChunkListener 监控
@Component
public class ChunkMonitorListener implements ChunkListener {
private long chunkStartTime;
@Override
public void beforeChunk(ChunkContext context) {
chunkStartTime = System.currentTimeMillis();
System.out.println("Chunk #" + context.getStepContext()
.getStepExecution().getCommitCount() + " 开始");
}
@Override
public void afterChunk(ChunkContext context) {
long duration = System.currentTimeMillis() - chunkStartTime;
int readCount = context.getStepContext().getStepExecution().getReadCount();
System.out.println("Chunk 完成,耗时: " + duration + "ms, "
+ "累计读取: " + readCount);
}
@Override
public void afterChunkError(ChunkContext context) {
System.out.println("Chunk 失败!回滚到上一个检查点");
}
}
运行结果:
Chunk #1 开始
Chunk 完成,耗时: 230ms, 累计读取: 500
Chunk #2 开始
Chunk 完成,耗时: 215ms, 累计读取: 1000
Chunk #3 开始
Chunk 失败!回滚到上一个检查点
→ Reader 指针回到第 1001 条,重新读 500 条
Chunk #3 开始(重试)
Chunk 完成,耗时: 260ms, 累计读取: 1500
易错场景与避坑
反例一:Chunk Size 过大导致 OOM
// ❌ Chunk Size=50000 → 内存中缓冲 5 万条对象 → OOM
.chunk(50000, tx)
正确做法:Chunk Size 通常设置在 10-1000 之间。大数据对象(含 BLOB)应使用更小的 Chunk Size。
反例二:Processor 中的操作影响事务时间
// ❌ Processor 中每条数据调一次外部 API(耗时 2s)
// Chunk Size=100 → 一个事务持续 200s → 数据库锁长期持有
@Override
public OrderEntity process(Order order) {
String result = slowExternalApi.call(order.getId()); // 2s
return transform(order, result);
}
面试高频考点
Q1:Chunk 的事务边界是什么?
一个 Chunk 的所有 Reader/Processor/Writer 操作在同一个事务中。Writer 成功 → COMMIT。Writer 失败 → 整个 Chunk ROLLBACK。Reader 指针在 COMMIT 时才推进。
Q2:Chunk Size 太小或太大的后果?
太小:事务数多,COMMIT 开销大,性能差。太大:内存占用高,事务时间长,数据库锁竞争激烈,回滚粒度粗。建议 50-500 之间根据单条数据大小调整。
上一章:CompositeItemWriter 详解下一章:作业参数与启动