乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 批处理概述与 Spring Batch 核心理念

    • Spring Batch 概述
    • Job-Instance-Execution 三层生命周期
    • 三层架构
    • Chunk 处理模型
    • Tasklet 处理模型
  • 第2章 Job与作业配置

    • Job 详解
    • Job 配置
    • JobLauncher 详解
    • JobParameters 详解
    • JobRepository 详解
  • 第3章 Step与执行模型

    • Step 详解
    • Step 配置
    • ExecutionContext 详解
    • ItemStream 与状态管理
    • StepScope 与 JobScope
  • 第4章 ItemReader数据读取

    • ItemReader 详解
    • FlatFileItemReader 详解
    • JdbcItemReader 详解
    • MultiResourceItemReader 详解
  • 第5章 ItemProcessor 数据处理

    • ItemProcessor 详解
  • 第6章 ItemWriter 数据写出

    • ItemWriter 详解
    • FlatFileItemWriter 详解
    • CompositeItemWriter 详解
    • JdbcBatchItemWriter 详解
  • 第7章 Chunk 处理与事务边界

    • Chunk 处理模型详解
    • 事务边界
  • 第8章 作业参数与启动

    • 命令行与 Web 启动
  • 第9章 监听器与拦截器

    • 监听器详解
  • 第10章 重启重试与跳过策略

    • 重启与重试
    • 跳过策略
  • 第11章 作业流与条件决策

    • 作业流与条件决策
  • 第12章 分区与并行处理

    • 分区详解
    • 并行 Step 与 Split
  • 第13章 与 Spring Boot 集成实践

    • Spring Boot 集成
  • 第15章 运维与监控

    • 大作业设计模式
    • 运维与监控

Chunk 处理模型

本章定位:彻底理解 Spring Batch 最核心的处理范式——Chunk-Oriented Processing。掌握 Chunk 的读写流程、事务边界和大小选择策略。

定义与作用

Chunk(数据块) 是 Spring Batch 面向块处理模式中的数据聚合单元。框架将逐条读取和处理的数据项累积到达到配置的 chunk-size 后,一次性地传递给 ItemWriter 进行批量写入。

飞翔科技架构师白歌在代码评审中经常强调:

"Chunk 不是简单的'每 N 条写一次'。它的关键设计是:在同一个事务内,逐条 read()、逐条 process(),最后一次性 write()。每一层都有独立的事务语义和容错策略。"

简而言之:

Chunk 处理 = 
  在一个事务中:
    for i in 1..chunkSize:
        item = ItemReader.read()     // 逐条读
        item = ItemProcessor.process(item)  // 逐条处理
        添加到缓冲区
    ItemWriter.write(缓冲区)          // 批量写
    提交事务

核心原理

Chunk 处理的数据流

为什么 Chunk 处理高效

对比维度逐条写入(Chunk Size = 1)Chunk 批量写入(Chunk Size = 50)
SQL 执行每行一次 INSERT(50 次网络往返)一次 batch INSERT 50 行(1 次网络往返)
事务提交每行 commit(50 次磁盘 I/O)每 chunk 一次 commit(1 次磁盘 I/O)
失败回滚仅当前行丢失整个 Chunk 回滚(最多 49 条已处理数据需要重做)
内存占用低(逐条释放)中(缓冲 chunk-size 条)

Chunk Size 选择策略

经验法则:

Chunk Size适用场景权衡
1需要逐条写入的强一致性场景性能最差,仅测试用
10 ~ 50通用场景,均衡性能与内存推荐默认范围
100 ~ 500高吞吐、数据处理简单内存占用增加
500 ~ 1000极大规模、内存充足事务时间长,失败回滚代价大

完整示例

场景一:飞翔科技学生管理系统——不同 Chunk Size 的性能对比

背景:小崔在优化学生数据导入性能时,产品经理孔蓝要求他在 30 秒内完成 10000 条数据的导入。他用不同 Chunk Size 做了基准测试。

测试配置:

@Bean
public Step importStep(JobRepository jobRepository,
                        PlatformTransactionManager tx,
                        ItemReader<Student> reader,
                        ItemWriter<Student> writer) {
    return new StepBuilder("importStep", jobRepository)
            .<Student, Student>chunk(50, tx)  // ← 测试 1 / 10 / 50 / 100 / 500
            .reader(reader)
            .writer(writer)
            .build();
}

性能测试结果:

Chunk Size总耗时事务数吞吐量CPU 占用内存峰值
145.2s10000221/s15%32MB
1012.8s1000781/s28%38MB
505.3s2001887/s42%45MB
1003.8s1002631/s55%58MB
5002.9s203448/s68%110MB

小崔的结论:Chunk Size 50 是最优平衡点——吞吐量是 Chunk Size 1 的 8.5 倍,但内存仅增加 13MB。

操作前后对比:

操作前(Chunk Size = 1):
  Step [importStep] completed: 10000 read, 10000 written, 10000 commits
  Duration: 45.2s

操作后(Chunk Size = 50):
  Step [importStep] completed: 10000 read, 10000 written, 200 commits
  Duration: 5.3s (↓ 88%)

场景二:电商订单处理——Chunk 内部分数据失败的处理

背景:运营部杨英处理订单 CSV 时,发现第 51 行数据格式异常。Chunk Size = 50 时,这条坏数据在第 2 个 Chunk 中,框架的行为是什么?

@Bean
public Step orderStep(JobRepository jobRepository,
                       PlatformTransactionManager tx,
                       FlatFileItemReader<Order> reader,
                       ItemWriter<Order> writer) {
    return new StepBuilder("orderStep", jobRepository)
            .<Order, Order>chunk(50, tx)
            .reader(reader)
            .writer(writer)
            .faultTolerant()
            .skip(FlatFileParseException.class)
            .skipLimit(5)
            .build();
}

行为分析:

运行输出:

Chunk 1: read 50 items, wrote 50 items, COMMIT
Chunk 2: read error at line 51 → SKIP (FlatFileParseException)
         read 49 more items (lines 52-101)
         wrote 49 items, COMMIT
         skipCount = 1
Final: readCount=100, writeCount=99, skipCount=1, Chunks=2

关键理解:当 Write 阶段发生异常(非 Read 阶段)时,框架会执行一个特殊流程——扫描整个 Chunk 逐条写出以定位具体哪条数据导致问题。这是一个代价较高的操作,因此应优先让 Read 和 Process 阶段的 Skip 策略拦截问题数据。

易错场景与避坑

反例一:Chunk Size 过大导致事务超时

小崔在测试环境调优时将 Chunk Size 设为 2000,但在生产环境遇到了事务超时:

// ❌ Chunk Size 2000 + 每条约 50ms = 事务持续 100 秒 → 超时
return stepBuilderFactory.get("step")
        .<Order, Order>chunk(2000)
        .reader(reader)
        .processor(slowProcessor) // 每条 50ms(含外部 API 调用)
        .writer(writer)
        .build();
// 错误:TransactionTimedOutException: Transaction timed out after 30 seconds

原因:每个 Chunk 是一个事务。Chunk Size 2000 × 50ms = 100 秒,超过了数据库事务超时时间。

解决方案:

// ✅ 根据单条处理时间反推 Chunk Size
// 单条耗时 50ms,期望事务最长 5s → Chunk Size ≤ 100
return new StepBuilder("step", jobRepository)
        .<Order, Order>chunk(100, tx)
        .reader(reader)
        .processor(slowProcessor)
        .writer(writer)
        .build();

反例二:误以为 Processor 返回 null = 终止 Step

前端开发黄俪在学 Spring Batch 时有个误解:

// ❌ 误解:return null 会导致 Step 终止
@Override
public StudentEntity process(Student item) {
    if (item.getAge() < 18) {
        return null; // 黄俪以为这会让 Step 停止
    }
    return transform(item);
}

实际行为:Processor 返回 null 只表示过滤当前记录——该条数据不会传递给 Writer,但 Reader 会继续读取下一条。Step 只在 Reader.read() 返回 null 时才结束。

ItemProcessor 的 null = "这条跳过",ItemReader 的 null = "数据读完了"。

面试高频考点

Q1:Chunk-Oriented Processing 的工作流程是什么?

在一个事务内:① ItemReader 逐条 read()(最多 chunk-size 次),② ItemProcessor 逐条 process(),③ 累积到 chunk-size 条后,一次性调用 ItemWriter.write(List) 批量写入,④ 提交事务。框架不关心每条的单个结果,只关心整个 Chunk 能否成功提交。

Q2:Chunk Size 如何选择?

核心公式:Chunk Size × 单条处理时间 < 预期事务最大时长。通用场景推荐 10~50,高吞吐场景 100~500。同时考虑内存占用(Chunk Size 越大,缓冲区占用越大)和失败回滚代价(Chunk 越大,失败后需要重做的数据越多)。

Q3:Process 阶段的过滤(return null)和 Skip 阶段的跳过有什么区别?

Process 返回 null 是正常过滤——开发者明确知道这条数据不需要处理,不会计入 skipCount。Skip 是异常容错——Reader/Processor/Writer 抛出指定异常时,框架自动跳过并计入 skipCount,达到 skipLimit 后 Step 失败。


上一章:Job-Instance-Execution 三层生命周期下一章:Tasklet 处理模型

上一页
三层架构
下一页
Tasklet 处理模型