使用Spring批处理2.2.1,我已经配置了一个Spring Batch Job,我使用了这种方法:
配置如下:
Tasklet使用ThreadPoolTaskExecutor限制为15个线程
throttle-limit等于线程数
Chunk与:
一起使用JdbcCursorItemReader的1个同步适配器,允许它按照Spring Batch文档推荐由许多线程使用
您可以将调用同步到read(),只要处理和写入是块中最昂贵的部分,您的步骤可能仍然比单线程配置快得多。
在JdbcCursorItemReader
基于JPA的Custom ItemWriter。 请注意,对一个项目的处理可能会因处理时间而异,可能需要几毫秒到几秒(> 60秒)。
将commit-interval设置为1(我知道它可能会更好,但不是问题)
关于Spring Batch doc recommandation
由于以下原因,运行批处理会导致非常奇怪和糟糕的结果:
查看Spring Batch代码,根本原因似乎在这个包中:
这种工作方式是一种功能还是限制/错误?
如果它是一个功能,配置的方式是什么方式使所有线程不受长处理工作的影响而不必重写所有内容?
请注意,如果所有项目都占用相同的时间,一切正常,多线程就可以了,但如果其中一项处理需要花费更多时间,那么多线程在慢速进程工作时几乎无用。
注意我打开了这个问题:
答案 0 :(得分:5)
正如亚历克斯所说,似乎这种行为是根据javadocs的契约:
子类只需要提供一个获取下一个结果*的方法,以及一个等待从并发*进程或线程返回所有结果的方法
看看:
TaskExecutorRepeatTemplate#waitForResults
另一个选择是使用分区:
Michael Minella在他的书Pro Spring Batch 的第11章中解释了这一点:
<batch:job id="batchWithPartition"> <batch:step id="step1.master"> <batch:partition partitioner="myPartitioner" handler="partitionHandler"/> </batch:step> </batch:job> <!-- This one will create Paritions of Number of lines/ Grid Size--> <bean id="myPartitioner" class="....ColumnRangePartitioner"/> <!-- This one will handle every partition in a Thread --> <bean id="partitionHandler" class="org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler"> <property name="taskExecutor" ref="multiThreadedTaskExecutor"/> <property name="step" ref="step1" /> <property name="gridSize" value="10" /> </bean> <batch:step id="step1"> <batch:tasklet transaction-manager="transactionManager"> <batch:chunk reader="myItemReader" writer="manipulatableWriterForTests" commit-interval="1" skip-limit="30000"> <batch:skippable-exception-classes> <batch:include class="java.lang.Exception" /> </batch:skippable-exception-classes> </batch:chunk> </batch:tasklet> </batch:step> <!-- scope step is critical here--> <bean id="myItemReader" class="org.springframework.batch.item.database.JdbcCursorItemReader" scope="step"> <property name="dataSource" ref="dataSource"/> <property name="sql"> <value> <![CDATA[ select * from customers where id >= ? and id <= ? ]]> </value> </property> <property name="preparedStatementSetter"> <bean class="org.springframework.batch.core.resource.ListPreparedStatementSetter"> <property name="parameters"> <list> <!-- minValue and maxValue are filled in by Partitioner for each Partition in an ExecutionContext--> <value>{stepExecutionContext[minValue]}</value> <value>#{stepExecutionContext[maxValue]}</value> </list> </property> </bean> </property> <property name="rowMapper" ref="customerRowMapper"/> </bean>
Partitioner.java:
package ...; import java.util.HashMap; import java.util.Map; import org.springframework.batch.core.partition.support.Partitioner; import org.springframework.batch.item.ExecutionContext; public class ColumnRangePartitioner implements Partitioner { private String column; private String table; public Map<String, ExecutionContext> partition(int gridSize) { int min = queryForInt("SELECT MIN(" + column + ") from " + table); int max = queryForInt("SELECT MAX(" + column + ") from " + table); int targetSize = (max - min) / gridSize; System.out.println("Our partition size will be " + targetSize); System.out.println("We will have " + gridSize + " partitions"); Map<String, ExecutionContext> result = new HashMap<String, ExecutionContext>(); int number = 0; int start = min; int end = start + targetSize - 1; while (start <= max) { ExecutionContext value = new ExecutionContext(); result.put("partition" + number, value); if (end >= max) { end = max; } value.putInt("minValue", start); value.putInt("maxValue", end); System.out.println("minValue = " + start); System.out.println("maxValue = " + end); start += targetSize; end += targetSize; number++; } System.out.println("We are returning " + result.size() + " partitions"); return result; } public void setColumn(String column) { this.column = column; } public void setTable(String table) { this.table = table; } }
答案 1 :(得分:3)
以下是我的想法:
换句话说,为了使Spring Batch中的这种多线程方法有所帮助,每个线程需要在大约相同的时间内处理。鉴于您的场景中某些项目的处理时间之间存在巨大差异,您遇到了一个限制,其中许多线程已完成并等待长时间运行的兄弟线程以便能够进入下一个处理块。
我的建议:
答案 2 :(得分:1)
在我的情况下,如果我没有设置限制限制,那么只有4个线程进入ItemReader的read()方法,这也是默认的线程数,如果未按照Spring Batch文档在tasklet标记中指定的话
如果我指定更多线程,例如10或20或100,那么只有8个线程进入ItemReader的read()方法
答案 3 :(得分:1)
无论throttle限制的值如何,8个活动线程的限制可能是由Spring Batch Job存储库上的争用引起的。每次处理一个块时,一些信息都会写入作业存储库。增加其池大小以适应您需要的线程数量!