Spring Batch问题:ItemProcessor未处理全部读取记录
我的Spring Batch批处理作业遇到了一个偶发的头疼问题:作业跑完后日志显示读取了198282条记录,但处理器里的前置日志只记录了196503条,不过有时候又能正常处理全部记录。作业是多线程运行的,throttle limit设为20,相关配置如下:
核心组件配置
ItemReader
用的是JpaPagingItemReader,已经设置saveState = false
ItemProcessor
class MyProcessor implements ItemProcessor<Item, Item> { @Override public Item process(final Item item) { log.info("action=process.."); // 其他业务处理逻辑 } }
Job配置
@Bean public Job myJob (Step myStep) { return jobBuilderFactory.get("myJob") .start(myStep) .build(); }
Step配置
@Bean public Step consolidateTaxaRebateJobStep ( JpaPagingItemReader<Item> reader, ItemProcessor<Item, Item> processor, ItemWriter<Item> writer, TaskExecutor taskExecutor) { return stepBuilderFactory.get("myStep") .<Item, Item>chunk(200) .reader(reader) .processor(processor) .writer(writer) .taskExecutor(taskExecutor) .throttleLimit(20) .build(); }
Spring Boot版本:2.0.1
想请教下:是Spring Batch没把所有记录发送到处理器,还是我在多线程使用上犯了错误?
这个偶发的记录“失踪”问题,我判断大概率和你用多线程+JpaPagingItemReader的组合配置有关,给你拆解几个关键原因和解决办法:
1. JpaPagingItemReader的线程安全隐患
JpaPagingItemReader本身不是线程安全的,当你用多线程TaskExecutor跑Step时,多个线程会共享同一个Reader实例。虽然你设置了saveState=false,但分页读取的核心状态(比如当前页码、分页参数)还是会被多线程并发修改,导致部分页码的记录被重复读取或者直接跳过——这就会出现日志里“读取总数”和“处理器接收数”对不上的情况,而且因为并发冲突是偶发的,所以有时候又能正常运行。
2. 多线程Step的Reader使用规范
Spring Batch有明确要求:多线程Step中使用的ItemReader必须是线程安全的,或者为每个线程提供独立的Reader实例。你现在直接把单例的JpaPagingItemReader注入到多线程Step里,刚好踩中了这个坑。
具体修复方案
针对这个问题,有两个常用的靠谱修复方式:
方案一:给Reader加上@Scope("step"),让每个线程用独立实例
修改你的JpaPagingItemReader的Bean定义,加上@Scope("step")注解,这样Spring Batch会为每个执行的Step线程创建一个独立的Reader实例,彻底避免并发冲突:
@Bean @Scope("step") public JpaPagingItemReader<Item> myReader() { // 这里初始化你的JpaPagingItemReader,设置saveState=false等参数 }
方案二:改用线程安全的游标式Reader
如果不想调整Bean作用域,也可以考虑换成JpaCursorItemReader——它是游标式读取而非分页,天生支持多线程场景下的安全读取,不过要注意游标式读取对数据库连接资源的占用情况,根据你的业务场景调整。
额外验证小提示
另外,你可以顺便检查下日志框架是不是有异步输出或者日志级别过滤的情况?不过从“偶发匹配、偶发不匹配”的现象来看,这个概率很低,核心问题还是Reader的线程安全问题。
内容的提问来源于stack exchange,提问作者melpin

