我有一个处理大约30K文件的Spring Boot / Batch应用程序。通过读入每个文件,使用各种正则表达式解析文件并将文件的位保存到数据库来完成文件的处理。它非常漂亮,可以在大约1小时内处理所有30K文件 - 除了应用程序初始化,这需要额外一小时的时间!
在我的批处理代码中,我正在设置一个分区步骤,并根据它们自己与简单正则表达式匹配的文件子集设置它的资源。获取这些资源似乎是性能杀手:如果我有少量文件,那么应用程序启动只需几秒钟 - 但是,如果我有30K文件,那么应用程序启动大约需要一个小时!
拿这段代码:
@Autowired
org.springframework.core.io.support.ResourcePatternResolver resolver;
String pattern = "file:/project_root/subfolder_*/logfile_*";
Resource[] resources = resolver.getResources(pattern);
当我使用2个子文件夹运行上面的操作时,每个子文件夹包含少量文件(例如,大约6个),应用程序会旋转并以很短的顺序执行。
但是,如果我将15K文件放入2个子文件夹中的每个子文件夹中,该应用程序只需要一个小时来初始化!处理所有文件大约需要一个小时 - 但由于所涉及的所有处理,这完全是预期的。
例如,在使用30K文件启动应用程序时,我日志中的最后几行是:
2014-12-30 13:53:10.284 INFO 1268 [main] --- o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor.
2014-12-30 13:53:11.152 INFO 1268 [main] --- o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=CasLogFileToDb_Job_2014-12-30T13:52:49.792-0700]] launched with the following parameters: [{}]
2014-12-30 13:53:12.706 INFO 1268 [main] --- o.s.batch.core.job.SimpleStepHandler : Executing step: [CasLogFileToDb_PartitioningStep_2014-12-30T13:52:49.797-0700]
2014-12-30 15:17:59.020 INFO 1268 [taskExecutor-6] --- o.u.c.c.processor.WorkflowManager : Starting to process file (/common/logs/cas/brandon-temp/test-prodback/cas1/catalina.out-20130626-131501.gz)
该应用程序然后在“执行步骤”中坐在那里大约一个小时,之后它开始在“开始处理文件” - 日志的前3行是Spring-Batch,最后一行是我的在实际开始处理文件之前的代码。
问题是:为什么getResources()需要一个小时? 任何人都使用getResources()与类似的大型文件集,并具有显着更快的性能?是否有其他方法可以获得这样的文件子集,但这可能会更快地执行?
顺便说一下,从命令行开始类似地列出文件大约需要10秒钟。 即:
ls -al project_root/subfolder_*/logfile_*
根据Michael的建议,我花了一些时间来分析代码的这个区域,以确定它是否与他链接的源相同。看来是这样。
我将大约3K文件上传到我的测试环境中(不是完整的30K值)。然后我在@BeforeJob的开头放置了一个断点,然后在我开始处理文件的代码的开头......然后在这两个点开始并停止了探查器... IOW:探测器捕获了之间发生的一切工作开始和个人任务开始。
结果如下:
从我的结果来看,排名前2位的CPU是一个MySQL调用SpringBatch调用。这是在我的任何代码都有机会进行任何数据库调用,甚至实际读取文件之前的全部内容。
所以,听起来好像是迈克尔提供的链接中描述的情况。 神秘的部分是,我使用的是Spring Boot 1.1.9.RELEASE,它反过来使用的是Spring Batch 3.0.2.RELEASE - 而Michael提供的链接表明这个bug已在Spring Batch 2.2.0中修复。
将我的Boot项目升级到最新版本(1.2.0.RELEASE)没有明显的效果。
回答迈克尔关于我所拥有的具有步骤范围的东西的问题......我有3个@StepScope方法。前两个实现了Batch的ItemReader和ItemWriter。第三个是my.WorkflowManager,它实现Batch的CompletionPolicy并负责监视单个文件的处理进程(即:跟踪进度数据,一旦文件处理完毕就移动文件,保持读取器状态以便回传一旦文件被读完就批处理等。)
@StepScope
@Bean
ItemReader reader(WorkflowManager workflowManager);
@StepScope
@Bean
ItemWriter writer(JdbcTemplate jdbcTemplate);
@StepScope
@Bean
public WorkflowManager workflowManager(@Value("#{stepExecutionContext['fileName']}") String gzipResourceLocation) {
return new WorkflowManager(gzipResourceLocation);
}
这是WorkflowManager(精简):
public class WorkflowManager implements CompletionPolicy, MyFileManager {
public WorkflowManager( String pathToFileGzip,
String pathToStagingFolder,
String pathToArchiveFolder) {
this.fileGzip = ResourceUtils.getFile(pathToFileGzip.trim()).toPath();
this.pathToStagingFolder = ResourceUtils.getFile(pathToStagingFolder.trim()).toPath();
this.pathToArchiveFolder = ResourceUtils.getFile(pathToArchiveFolder.trim()).toPath();
}
@BeforeStep
public void beforeStep(StepExecution stepExecution);
@AfterStep
public void afterStep(StepExecution stepExecution);
@Override
public void readingIsComplete();
@Override
public boolean isReadingComplete();
@Override
public boolean isComplete(RepeatContext context, RepeatStatus status);
@Override
public boolean isComplete(RepeatContext context);
@Override
public RepeatContext start(RepeatContext parent);
@Override
public void update(RepeatContext context);
@Override
public BufferedReader getFileAsBufferedReader();
}