【问题标题】:AsyncListItemWriter step end detected early提前检测到 AsyncListItemWriter 步骤结束
【发布时间】:2021-03-16 09:48:37
【问题描述】:

我已经编写了 ItemWriter 的异步版本来异步写入我的项目:

public class AsyncListItemWriter<T> implements ItemStreamWriter<T>, InitializingBean {

    private ItemWriter<T> delegate;

    private TaskExecutor taskExecutor = new SyncTaskExecutor();

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(delegate, "A delegate ItemWriter must be provided.");
    }

    public void setDelegate(ItemWriter<T> delegate) {
        this.delegate = delegate;
    }

    public void setTaskExecutor(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    @Override
    public void open(ExecutionContext executionContext) throws ItemStreamException {
        if (delegate instanceof ItemStream) {
            ((ItemStream) delegate).open(executionContext);
        }
    }

    @Override
    public void update(ExecutionContext executionContext) throws ItemStreamException {
        if (delegate instanceof ItemStream) {
            ((ItemStream) delegate).update(executionContext);
        }
    }

    @Override
    public void close() throws ItemStreamException {
        if (delegate instanceof ItemStream) {
            ((ItemStream) delegate).close();
        }
    }

    @Override
    public void write(List<? extends T> items)  {
        StepExecution stepExecution = getStepExecution();
        taskExecutor.execute(() -> {
            if (stepExecution != null) {
                StepSynchronizationManager.register(stepExecution);
            }
            try {
                delegate.write(items);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (stepExecution != null) {
                    StepSynchronizationManager.close();
                }
            }
        });
    }

    private StepExecution getStepExecution() {
        StepContext context = StepSynchronizationManager.getContext();
        if (context == null) {
            return null;
        }
        StepExecution stepExecution = context.getStepExecution();
        return stepExecution;
    }
}

配置:

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(64);
        executor.setMaxPoolSize(64);
        executor.setQueueCapacity(64);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadNamePrefix("MultiThreaded-");
        return executor;
    }

    @Bean
    public ItemWriter<STModel> writer(){
        return items -> {
            Thread.sleep(1000);
            System.out.println("Writing...");
            for(STModel c : items) {
                System.out.println("######### Writer : ------> " + c + " inside size : " + c.relation.size() + ", On : " +Thread.currentThread().getName());
            }
        };
    }

    @Bean
    public AsyncListItemWriter<STModel> asyncWriter() throws Exception {
        AsyncListItemWriter<STModel> asyncItemWriter = new AsyncListItemWriter<>();
        asyncItemWriter.setDelegate(writer());
        asyncItemWriter.setTaskExecutor(taskExecutor());
        asyncItemWriter.afterPropertiesSet();
        return asyncItemWriter;
    }

    @Bean
    public Step sampleStep() throws Exception{
        return stepBuilderFactory.get("processingStep")
                .<STModel, STModel>chunk(10)
                .reader(itpReader())
                .writer(asyncWriter())
                .build();
    }

    @Bean
    public Job job() throws Exception{
        return jobBuilderFactory.get("job")
                .start(sampleStep())
                .build();
    }

在阅读完所有文件后,我收到了这个日志(文件在主线程上读取):

同时读取 ---->STModel(tripId=109138356-1_459178)
同读时---->STModel(tripId=109138356-1_459178)
虽然相同读取 ---->null
读取---->null,打开:main
2021-03-16 10:38:48.409 INFO 17042 --- [main] os.batch.core.step.AbstractStep:步骤:[processingStep] 在 337 毫秒内执行
2021-03-16 10:38:48.412 INFO 17042 --- [main] o.s.b.c.l.support.SimpleJobLauncher:作业:[SimpleJob:[name=job]] 已完成,参数如下:[{}] 和以下状态:[已完成] 在 349 毫秒内
写作...
######### 编写器:------> STModel(tripId=109138355-1_459164) 内部大小:2,开启:MultiThreaded-1
######### 编写者:------> STModel(tripId=109138355-1_459165) 内部大小:1,开启:MultiThreaded-1
######### 编写器:------> STModel(tripId=109138355-1_459166) 内部大小:1,开启:MultiThreaded-1
######### 编写器:------> STModel(tripId=109138355-1_459167) 内部大小:1,开启:MultiThreaded-1
######### 编写器:------> STModel(tripId=109138355-1_459168) 内部大小:2,开启:MultiThreaded-1
######### 编写器:------> STModel(tripId=113507833-1_38959) 内部尺寸:23,开启:MultiThreaded-1
######### 编写器:------> STModel(tripId=113507835-1_38960) 内部尺寸:23,开启:MultiThreaded-1
######### 编写器:------> STModel(tripId=113507852-1_38961) 内部尺寸:23,开启:MultiThreaded-1
######### 编写器:------> STModel(tripId=113507863-1_38962) 内部尺寸:23,开启:MultiThreaded-1
######### 编写器:------> STModel(tripId=113507871-1_38963) 内部尺寸:23,开启:MultiThreaded-1
写作...
######### 编写器:------> STModel(tripId=113507882-1_38964) 内部大小:23,开启:MultiThreaded-2
######### 编写器:------> STModel(tripId=113507890-1_38965) 内部尺寸:23,开启:MultiThreaded-2
######### 编写器:------> STModel(tripId=113507900-1_38966) 内部尺寸:23,开启:MultiThreaded-2
######### Writer : ------> STModel(tripId=113507911-1_38967) inside size : 23, On : MultiThreaded-2


如您所见,spring batch 过早检测到 steap 的结束,就在读取操作之后。

如何告诉 spring batch 步骤结束是在所有写入任务完成之后?

【问题讨论】:

  • 异步在后台运行,所以只要一切都在后台,对于 Spring Batch,写入已经完成。如果这不是您想要的,请不要使用异步,或者编写一个适当的异步编写器来等待所有任务完成。
  • 是否有另一种方法可以通过仅使用 spring 批处理组件来执行此异步写入?
  • 如果仍然要等待所有线程,为什么还需要异步写入。
  • 我想在处理完所有项目后发送邮件。现在邮件发送得太早了
  • 如果您仍然要等待它,为什么还需要异步写入?它增加了什么?

标签: java spring spring-batch


【解决方案1】:

如你所见,spring batch 过早检测到 steap 的结束,

这是您应该期待的,因为您的编写器在后台异步写入项目而不等待它们完成。一旦作者的write 方法返回,该步骤将继续读取下一个项目块,如果没有更多项目要读取,则可能会完成。这可能发生在前一个块由您的异步任务执行器在后台写入之前。

如何告诉 spring batch 步骤结束是在所有写入任务完成之后?

在您的编写器中,您需要submit 任务(而不是execute 他们),获取他们的Futures 的句柄并等待他们完成Future.get(如果需要,超时)。

编辑:添加示例

@Override
public void write(List<? extends T> items) {
    List<Future<?>> futures = new ArrayList<>(items.size());
    // write items in parallel (note the singleton list passed to the delegate)
    for (T item : items) { 
        Future<?> future = taskExecutor.submit(() -> {
            try {
                delegate.write(Collections.singletonList(item));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        futures.add(future);
    }
    // wait for futures to finish
    futures.forEach(future -> {
        try {
            future.get(10, TimeUnit.SECONDS);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });
}

我在这里看到的唯一优点是项目将并行编写。这可以加快您为每个项目发送电子邮件的用例的速度。但是,我从您的 cmets 中看到以下内容:

写入与读取并发,因此可以加快处理速度!

这是不正确的,读取下一个块仍然会等待当前的写入操作完成。所以如上所述,您需要等待写入操作在您的写入器中完成,否则您的工作可能看起来已经完成,而项目仍在后台写入。

如果你真的希望读取和写入都由多个线程同时完成,你需要使用多线程步骤:

@Bean
public Step sampleStep() throws Exception{
    return stepBuilderFactory.get("processingStep")
                .<STModel, STModel>chunk(10)
                .reader(itpReader())
                .writer(writer())
                .taskExecutor(taskExecutor())
                .build();
}

另一种选择是使用并发步骤,如本期所述:https://github.com/spring-projects/spring-batch/issues/2044。如果您有兴趣,我有一个使用 BlockingQueue 作为暂存区 here 的 PoC。

【讨论】:

  • 我在答案中添加了一个示例。
  • your code in the answer is like AsyncItemWriter:不,不一样。您的 AsyncItemWriter 提交一个循环遍历项目的任务。答案中的代码不会逐个写入项目,而是并行写入:每个线程将写入一个项目。它为每个项目提交一个任务。这将比您在 for 循环中编写项目更快。
  • 我刚刚看到你的 PoC,它的优点,这就是我要找的 :)
  • 太好了,很高兴它有帮助。
  • 嗨,你能看看stackoverflow.com/questions/69347193/…吗?谢谢:)
猜你喜欢
  • 1970-01-01
  • 2014-05-17
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 2019-10-27
  • 2021-04-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多