1. SpringBatch 介绍
Spring Batch是spring的一个子项目,基于spring框架开发。提供了大量的可重用组件,包括日志、追踪、事务、任务 作业统计、重启、跳过等。能够处理简单的、复杂的和大数据量的批处理作业。Spring Batch只关注处理任务相关的问题 ,如事务,并发,监控等,需要调度框架来协作构建完成批处理任务(如 利用Quartz定时调度框架进行任务调度)
Spring Batch 框架组成:
1)、jobLauncher:任务启动器(该接口只有一个run方法)、通过它来启动任务、可以看做是程序的入口.
2)、job:代表着一个具体的任务
3)、step:代表着一个具体的步骤,一个job可以包含多个step,每个step都有一个ItemReader(读取数据),一个ItemProcessor(处理数据),和一个ItemWriter(写入数据)。在实际场景中,一个任务可能会很复杂,这个时候可以将任务拆分为多个Step,分别对这些step进行管理(复杂任务简单化),step默认是串行执行,也可以更改为并行执行。
4)、jobRepository:存储数据的地方,可以看做是一个数据库的接口,在任务执行的时候需要通过他来记录任务的状态等信息。
关键词描述:
Spring Batch 流程介绍:
每个batch中都会包含 一个Job,Job就像一个容器,这个容器里装了若干step,Batch中实际工作的就是这些Step,(读取数据-->处理数据-->写入数据)。外部控制器(定时器控制)调用JobLauncher启动一个Job,Job调用自己的Step去实现对数据的操作,Step树立完成后再将处理结果一步步返回给上一层,这就是Batch处理实现的一个简单流程。
实际应用中数据来源不同,实现接口不同,但读写过程操作近似一致。选择哪个实现接口,根据需求来确定。
此处指列举几个常用的。
- JdbcPagingItemReader :从数据库中读取数据
- FlatFileItemReader:从CVS文件中读取数据
- StaxEventItemReader:从XML文件中读取数据
- MultiResourceItemReader:从多个文件读取数据
2. Spring Batch 异常处理
1). skip 跳过
在一个step中,不管reader还是process,还是write,出现了指定的错误都可以跳过,继续执行后面的数据。
public Step barrageTextOpinionArchiveSentimentStep() throws InstantiationException, IllegalAccessException {
return stepBuilderFactory.get("barrageTextOpinionArchiveSentimentStep")
.<BarrageTextOpinion, BarrageTextOpinion> chunk(SENTIMENT_CHUNK_SIZE)
.reader(barrageTextOpinionArchiveSentimentFileReader())
.writer(barrageTextOpinionArchiveSentimentChainWriter())
.faultTolerant()
.processorNonTransactional()
.skip(Exception.class) //跳过IOException类型的异常
.skipLimit(100) //指定做多跳过5次
.listener(stepStatusListener)
.listener(exceptionLoggingSkipListener)
.allowStartIfComplete(true)
.build();
}
2). retry 重试
当Spring Batch在处理批量时,有时因为某些原因,使批量在第一次执行时出错,比如锁表之类的,出错之后,可以进行(重试)多次执行出错的地方,如果重试每次都失败,批量才失败。
@Bean
public Job fileReaderJob(){
return jobBuilderFactory.get("fileReaderJob")
.start(chunkStep())
.build();
}
@Bean
public Step chunkStep(){
return stepBuilderFactory.get("chunkStep1")
.chunk(3) // 块大小
.reader(fileItemReader())
.writer(fileItemWriter())
.faultTolerant()
/**.retry(LockObtainFailedException.class)
.retryLimit(1)*/
.retry(FlatFileParseException.class)
//遇到FlatFileParseException类型的异常时,运行重复执行3次,如果3次都失败,批量才失败
.retryLimit(3)
.noRetry(NullPointerException.class)
//遇到NullPointerException类型的异常时,不重复执行
.allowStartIfComplete(true)
.build();
}
skip与retry的区别:retry不能对reader抛出的异常进行retry,只能对process或者write抛出的异常进行retry;skip可以对reader或者process或者write抛出的异常进行skip。
3). restart 重启
1、正常情况下,重启相同参数的job,会报错,提示该job已存在。一般设置job id自增或在job参数中加上系统时间进行区分,完成重启。
2、在step中阻止重启
如果Job允许重启,但step不允许重启,当重启job后,会跳过不允许重启的step,不会报错
@Bean
public Job demoTestJob(){
return jobBuilderFactory.get("demoTestJob")
.listener(jobStatusListener)
.start(demoTestStep())
.build();
}
@Bean
public Step demoTestStep(){
return stepBuilderFactory.get("demoTestStep")
.listener(stepStatusListener)
.<User,User>chunk(1)
.reader(demoReader())
.writer(demoWriter())
// .allowStartIfComplete(true)
.allowStartIfComplete(false)
.build();
}
当重启Job后,在日志表中可以看到job的status是COMPLETED,exit_code是NOOP,exit_message是 All steps already completed or no steps configured for this job.
3、重启次数
@Bean
public Job demoTestJob(){
return jobBuilderFactory.get("demoTestJob")
.listener(jobStatusListener)
.start(demoTestStep())
.build();
}
@Bean
public Step demoTestStep(){
return stepBuilderFactory.get("demoTestStep")
.listener(stepStatusListener)
.<User,User>chunk(1)
.reader(demoReader())
.writer(demoWriter())
.startLimit(10) //设置有异常时,重启次数是3次,如果没异常,不重启
.build();
}
当该job作业没有异常时,job不管运行多少次,step都只在第一次运行;如果该job作业有异常,当job执行第一次在step中抛出异常后,当后面重启job后,step从上次异常中断的地方重启,如果还是异常,一直到3次后,就不再重启,提示已经超过最大执行次数:Maximum start limit exceeded for step: chunkStep1StartMax: 10 。
4、job成功后,也允许重启
前三种都是当job出现异常,重启后,从抛出异常的地方继续执行。而下面情况,每次重启job后,都是从开始的地方执行。
@Bean
public Job demoTestJob(){
return jobBuilderFactory.get("demoTestJob")
.listener(jobStatusListener)
.start(demoTestStep())
.build();
}
@Bean
public Step demoTestStep(){
return stepBuilderFactory.get("demoTestStep")
.listener(stepStatusListener)
.<User,User>chunk(1)
.reader(demoReader())
.writer(demoWriter())
.allowStartIfComplete(true) // job任务执行完成后,重启再次执行该任务
.build();
}
3.自定义监听器
jobStatusListener
package com.lxj.config;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.stereotype.Component;
import java.util.Iterator;
import java.util.Map;
@CommonsLog
@Component
public class JobStatusListener implements JobExecutionListener {
@Override
public void beforeJob(JobExecution jobExecution) {
}
@Override
public void afterJob(JobExecution jobExecution) {
StringBuilder buf = new StringBuilder();
buf.append("\nJob " + jobExecution.getJobInstance().getJobName() + " \n");
buf.append(" Started : " + jobExecution.getStartTime() + "\n");
buf.append(" Finished : " + jobExecution.getEndTime() + "\n");
buf.append(" Exit-Code : " + jobExecution.getExitStatus().getExitCode() + "\n");
buf.append(" Exit-Descr. : " + jobExecution.getExitStatus().getExitDescription() + "\n");
buf.append("Job-Parameter : \n");
JobParameters jp = jobExecution.getJobParameters();
for (Iterator<Map.Entry<String, JobParameter>> iter = jp.getParameters().entrySet().iterator(); iter.hasNext(); ) {
Map.Entry<String, JobParameter> entry = iter.next();
buf.append(" " + entry.getKey() + "=" + entry.getValue() + "\n");
}
log.info(buf.toString());
}
}
StepStatusListener
package com.lxj.config;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@CommonsLog
@Component
public class StepStatusListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
StringBuilder buf = new StringBuilder();
long duration = System.currentTimeMillis() - stepExecution.getStartTime().getTime();
buf.append("Step " + stepExecution.getStepName() + " \n");
buf.append(" Spent (m) : " + TimeUnit.MILLISECONDS.toMinutes(duration) + "\n");
buf.append(" WriteCount: " + stepExecution.getWriteCount() + "\n");
buf.append(" Commits : " + stepExecution.getCommitCount() + "\n");
buf.append(" SkipCount : " + stepExecution.getSkipCount() + "\n");
buf.append(" Rollbacks : " + stepExecution.getRollbackCount() + "\n");
buf.append(" Filter : " + stepExecution.getFilterCount() + "\n");
log.info(buf.toString());
return stepExecution.getExitStatus();
}
}
Others' knowledge of springbatch
https://blog.csdn.net/guo_xl/article/details/83444983
https://www.jdon.com/springboot/spring-batch-partition.html