\
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情。
SpringBatch从入门到精通-2-StepScope作用域和用法【掘金日新计划】
SpringBatch从入门到精通-3-并行处理【掘金日新计划】
SpringBatch从入门到精通-3.2-并行处理-远程分区【掘金日新计划】
SpringBatch从入门到精通-3.3-并行处理-远程分区(消息聚合)【掘金日新计划】
SpringBatch从入门到精通-4 监控和指标【掘金日新计划】
SpringBatch从入门到精通-4.2 监控和指标-原理【掘金日新计划】
SpringBatch从入门到精通-5 数据源配置相关【掘金日新计划】
SpringBatch从入门到精通-5.2 数据源配置相关-原理【掘金日新计划】
SpringBatch从入门到精通-6 读和写处理【掘金日新计划】
SpringBatch从入门到精通-6.1 读和写处理-实战【掘金日新计划】
SpringBatch从入门到精通-6.2 读和写处理-实战2【掘金日新计划】
SpringBatch从入门到精通-6.3 读和写处理-实战3【掘金日新计划】
SpringBatch从入门到精通-6.4 读和写处理-实战4【掘金日新计划】
SpringBatch从入门到精通-7 常见的批处理的一些模式【掘金日新计划】
SpringBatch从入门到精通-8 数据处理【掘金日新计划】
SpringBatch从入门到精通-9 重复处理【掘金日新计划】
SpringBatch从入门到精通-10 重试处理【掘金日新计划】
背景
有个需求:领导需要job执行的时候,提前返回jobId相关信息。
目的:job执行的时候如果发生宕机事件,jobId还能记录在任务表中。
储备知识: 知道原生JobLanucher机制,原生如果不满足,知道如何扩展。
JobLauncher
public interface JobLauncher {
/**
为给定的{@link job}和{@link JobParameters}启动作业执行如果{@link JobExecution}能够成功创建,它将
无论执行成功。如果有过去的{@link JobExecution}已暂停,则返回相同的{@link JobExecution},而不是新的
已创建一个。只有在以下操作失败时才会引发异常开始作业。如果作业在处理过程中遇到错误,则
将返回JobExecution,并需要检查其状态
*/
public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException,
JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}
解释: 给一个job和对应的jobParameters 将job运行起来。
该接口现在给了一个默认实现SimpleJobLauncher
该接口可以使用的时候。可以直接用new的方式。也可以是用把他当作一个bean的形式。
因为在代码里已经做注入了。
如果使用new的方式需要将jobRepository 传进来。
默认如果不传入taskExecutor的时候会创建一个同步执行器。
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(jobRepository != null, "A JobRepository has not been set.");
if (taskExecutor == null) {
logger.info("No TaskExecutor has been set, defaulting to synchronous executor.");
taskExecutor = new SyncTaskExecutor();
}
}
所以还可以支持传入一个异步的执行器来异步执行,提前返回jobExecution。该提前量中包含了jobId,jobExeId等关键信息
JobExecution: id=131, version=1, startTime=Sat Jun 25 23:38:47 CST 2022, endTime=null, lastUpdated=Sat Jun 25 23:38:47 CST 2022, status=STARTED, exitStatus=exitCode=UNKNOWN;exitDescription=, job=[JobInstance: id=131, version=0, Job=[asyncJobLanucherJob]], jobParameters=[{date=1656171527032, rnd=1511d04b-70e1-4157-b447-b6ed51058ec7}]
可行性方案
现有方案不能直接又同步执行,又最大限度的减少数据库访问来达到提前知道结果。
方案1: 同步JobLauncher执行器+ 异步线程查询job执行id情况
方案2: 异步JobLauncher执行器(主动传入taskExecutor)+ 同步主线程轮询查询数据库情况。
方案3: 自定义同步JobLauncher执行器+CompletableFuture allOf 执行结果
方案4: 自定义同步JobLauncher执行器+同步调用接口(推荐方案)
对比之下:方案1和方案2 都不是很好。方案3 :代码如下
public class TaskAsyncJobLauncher implements JobLauncher, InitializingBean {
protected static final Log logger = LogFactory.getLog(SimpleJobLauncher.class);
private JobRepository jobRepository;
private TaskExecutor taskExecutor;
private JobExecution jobExecutionCreate;
@Override
public JobExecution run(final Job job, final JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException,
JobParametersInvalidException {
Assert.notNull(job, "The Job must not be null.");
Assert.notNull(jobParameters, "The JobParameters must not be null.");
final JobExecution jobExecution;
JobExecution lastExecution = jobRepository.getLastJobExecution(job.getName(), jobParameters);
if (lastExecution != null) {
if (!job.isRestartable()) {
throw new JobRestartException("JobInstance already exists and is not restartable");
}
/*
* validate here if it has stepExecutions that are UNKNOWN, STARTING, STARTED and STOPPING
* retrieve the previous execution and check
*/
for (StepExecution execution : lastExecution.getStepExecutions()) {
BatchStatus status = execution.getStatus();
if (status.isRunning() || status == BatchStatus.STOPPING) {
throw new JobExecutionAlreadyRunningException("A job execution for this job is already running: "
+ lastExecution);
} else if (status == BatchStatus.UNKNOWN) {
throw new JobRestartException(
"Cannot restart step [" + execution.getStepName() + "] from UNKNOWN status. "
+ "The last execution ended with a failure that could not be rolled back, "
+ "so it may be dangerous to proceed. Manual intervention is probably necessary.");
}
}
}
// Check the validity of the parameters before doing creating anything
// in the repository...
job.getJobParametersValidator().validate(jobParameters);
/*
* There is a very small probability that a non-restartable job can be
* restarted, but only if another process or thread manages to launch
* <i>and</i> fail a job execution for this instance between the last
* assertion and the next method returning successfully.
*/
jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters);
setJobExecutionCreate(jobExecution);
try {
taskExecutor.execute(new Runnable() {
@Override
public void run() {
try {
if (logger.isInfoEnabled()) {
logger.info("Job: [" + job + "] launched with the following parameters: [" + jobParameters
+ "]");
}
job.execute(jobExecution);
if (logger.isInfoEnabled()) {
Duration jobExecutionDuration = BatchMetrics.calculateDuration(jobExecution.getStartTime(), jobExecution.getEndTime());
logger.info("Job: [" + job + "] completed with the following parameters: [" + jobParameters
+ "] and the following status: [" + jobExecution.getStatus() + "]"
+ (jobExecutionDuration == null ? "" : " in " + BatchMetrics.formatDuration(jobExecutionDuration)));
}
}
catch (Throwable t) {
if (logger.isInfoEnabled()) {
logger.info("Job: [" + job
+ "] failed unexpectedly and fatally with the following parameters: [" + jobParameters
+ "]", t);
}
rethrow(t);
}
}
private void rethrow(Throwable t) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
else if (t instanceof Error) {
throw (Error) t;
}
throw new IllegalStateException(t);
}
});
}
catch (TaskRejectedException e) {
jobExecution.upgradeStatus(BatchStatus.FAILED);
if (jobExecution.getExitStatus().equals(ExitStatus.UNKNOWN)) {
jobExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(e));
}
jobRepository.update(jobExecution);
}
return jobExecution;
}
/**
* Set the JobRepository.
*
* @param jobRepository instance of {@link JobRepository}.
*/
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
/**
* Set the TaskExecutor. (Optional)
*
* @param taskExecutor instance of {@link TaskExecutor}.
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* Ensure the required dependencies of a {@link JobRepository} have been
* set.
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(jobRepository != null, "A JobRepository has not been set.");
if (taskExecutor == null) {
logger.info("No TaskExecutor has been set, defaulting to synchronous executor.");
taskExecutor = new SyncTaskExecutor();
}
}
public JobExecution getJobExecutionCreate() {
return jobExecutionCreate;
}
public void setJobExecutionCreate(JobExecution jobExecutionCreate) {
this.jobExecutionCreate = jobExecutionCreate;
}
}
主要改造点是增加了一个变量
private JobExecution jobExecutionCreate;
setJobExecutionCreate(jobExecution);
如何使用:
@Test
public void asyncJobLanucherJobTest()
{
Job job =ctx.getBean("asyncJobLanucherJob", Job.class);
JobParametersBuilder jobParametersBuilder =new JobParametersBuilder();
jobParametersBuilder.addDate("date",new Date());
jobParametersBuilder.addString("rnd", UUID.randomUUID().toString());
TaskAsyncJobLauncher taskAsyncJobLauncher = asyncConfiguration.AsyncJobLauncher();
CompletableFuture<JobExecution> jobExecutionCreate = CompletableFuture.supplyAsync(() -> {
while (taskAsyncJobLauncher.getJobExecutionCreate()==null){
try {
System.out.println("继续等待100ms");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("提前获取到创建jobExecution:"+taskAsyncJobLauncher.getJobExecutionCreate());
return taskAsyncJobLauncher.getJobExecutionCreate();
});
CompletableFuture<JobExecution> jobRunning = CompletableFuture.supplyAsync(() -> {
try {
// throw new RuntimeException("11=========");
return taskAsyncJobLauncher.run(job,jobParametersBuilder.toJobParameters());
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("运行失败==");
return null;
});
CompletableFuture<Void> end = jobExecutionCreate.thenAcceptBoth(jobRunning, (a, b) -> {
// System.out.println(a+"准备,下" + b);
System.out.println("运行成功=="+b);
});
end.join();
}
可以达到要求。
方案4改造:增加个同步调用接口。
public interface jobExecutionCreateService {
void jobExecutionCreateOperate(JobExecution jobExecution);
}
如何使用
jobExecutionCreateService.jobExecutionCreateOperate(jobExecution);
最后还是方案4:比较优雅一些。用户只需要实现接口即可。还是同步执行的。
代码位置: github.com/jackssybin/…