Spring Batch JSR-352 重启错误的分区
Spring Batch JSR-352 restarts the wrong partitions
我使用 Spring Batch JSR-352 实现创建了一个简单的批处理。该批处理使用 PartitionMapper 为每个块注入一个单独的 属性。
根据 JSR-352 规范(当 partitionsOverride = False 时)当其中一个分区块失败并且批处理重新启动时 只有 失败的分区块应该重新启动。
例如,如果我们有 3 个分区:partition0、partition1 和 partition2。
如果 partition1 和 partition2 失败,批处理应该只用它们自己的批处理属性重新启动 partition1 和 partion2。
但是我注意到,当使用 Spring Batch JSR-352 实现(最新版本 3.0.3.Release)时,重新启动批处理将重新启动 partition0 和 partition1 而不是 partition1 和 partition2。因此,它正确地检测到两个分区发生故障,但它错误地重新启动了前(两个)分区,而不是它应该重新启动的失败分区。
这是 Spring 批处理实施中的错误还是我遗漏了什么?
参见 JSR-352 文档第 10.8.5 节:
http://download.oracle.com/otn-pub/jcp/batch-1_0_revA-mrel-spec/JSR_352-v1.0_Rev_a-Maintenance_Release.pdf
这是我使用的代码:
/META-INF/batch-jobs/sampleBatch.xml
<?xml version="1.0" encoding="UTF-8"?>
<job id="sampleBatch" version="1.0" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd">
<step id="sampleStep">
<chunk item-count="100">
<reader ref="com.springapp.batch.SampleReader">
<properties>
<property name="sample" value="#{partitionPlan['sample']}"/>
</properties>
</reader>
<writer ref="com.springapp.batch.SampleWriter">
<properties>
<property name="sample" value="#{partitionPlan['sample']}"/>
</properties>
</writer>
</chunk>
<partition>
<mapper ref="com.springapp.batch.SamplePartitionMapper"/>
</partition>
</step>
</job>
com.springapp.batch.SamplePartitionMapper:
package com.springapp.batch;
import java.util.Properties;
import javax.batch.api.partition.PartitionMapper;
import javax.batch.api.partition.PartitionPlan;
import javax.batch.api.partition.PartitionPlanImpl;
public class SamplePartitionMapper implements PartitionMapper {
@Override
public PartitionPlan mapPartitions() throws Exception {
final PartitionPlan partitionPlan = new PartitionPlanImpl();
int size = 3;
Properties[] partitionProps = new Properties[size];
for (int i=0; i<size; i++) {
final Properties properties = new Properties();
properties.put("sample", ""+i);
partitionProps[i] = properties;
System.out.println("mapPartitions: " + i);
}
partitionPlan.setThreads(1);
partitionPlan.setPartitions(partitionProps.length);
partitionPlan.setPartitionProperties(partitionProps);
return partitionPlan;
}
}
com.springapp.batch.SampleReader:
public class SampleReader extends AbstractItemReader {
@Inject
@BatchProperty
private String sample;
Iterator<Integer> iter;
@Override
public void open(Serializable checkpoint) throws Exception {
System.out.println("open for reading sample: " + sample);
ArrayList list = new ArrayList<Integer>();
for(int i=0; i<Integer.parseInt(sample); i++) {
list.add(new Integer(i));
}
iter = list.iterator();
}
@Override
public Integer readItem() throws Exception {
if(iter.hasNext())
return iter.next();
else
return null;
}
}
com.springapp.batch.SampleWriter:
public class SampleWriter extends AbstractItemWriter {
@Inject
@BatchProperty
private String sample;
@Override
public void writeItems(List<Object> items) throws Exception {
System.out.println("writeItems sample: " + sample);
if(sample.equals("1")) {
throw new Exception("FAIL PARTITION 1");
}
if(sample.equals("2")) {
throw new Exception("FAIL PARTITION 2");
}
for (Object itemObj : items) {
Integer item = (Integer) itemObj;
System.out.println(item);
}
}
}
TestJob 测试运行者:
package com.springapp.batch;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.batch.operations.JobOperator;
import javax.batch.runtime.BatchRuntime;
import javax.batch.runtime.JobExecution;
import javax.inject.Inject;
import junit.framework.Assert;
import org.junit.Test;
//@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:spring-config.xml")
public class AppTests {
@Test
public void testJob() throws Exception {
JobOperator jobOperator = BatchRuntime.getJobOperator();
long jobExecution = jobOperator.start("sampleBatch", new Properties());
int attempt = 0;
while (true) {
JobExecution execution = jobOperator.getJobExecution(jobExecution);
if (execution.getEndTime() != null) {
//check status
if( "FAILED".equals(execution.getExitStatus()) && attempt < 3 ) {
attempt++;
System.out.println("Batch failed, trying to restart (attempt " + attempt + ")..");
jobExecution = jobOperator.restart(jobExecution, new Properties());
continue;
}
System.out.println("Batch ended with status: " + execution.getExitStatus());
break;
}
}
Assert.assertEquals("COMPLETED", jobOperator.getJobExecution(jobExecution).getExitStatus());
}
}
看着这个,它看起来像一个错误。我已经记录了 Jira 问题 BATCH-2364 来跟踪它。
我使用 Spring Batch JSR-352 实现创建了一个简单的批处理。该批处理使用 PartitionMapper 为每个块注入一个单独的 属性。 根据 JSR-352 规范(当 partitionsOverride = False 时)当其中一个分区块失败并且批处理重新启动时 只有 失败的分区块应该重新启动。
例如,如果我们有 3 个分区:partition0、partition1 和 partition2。 如果 partition1 和 partition2 失败,批处理应该只用它们自己的批处理属性重新启动 partition1 和 partion2。
但是我注意到,当使用 Spring Batch JSR-352 实现(最新版本 3.0.3.Release)时,重新启动批处理将重新启动 partition0 和 partition1 而不是 partition1 和 partition2。因此,它正确地检测到两个分区发生故障,但它错误地重新启动了前(两个)分区,而不是它应该重新启动的失败分区。
这是 Spring 批处理实施中的错误还是我遗漏了什么?
参见 JSR-352 文档第 10.8.5 节: http://download.oracle.com/otn-pub/jcp/batch-1_0_revA-mrel-spec/JSR_352-v1.0_Rev_a-Maintenance_Release.pdf
这是我使用的代码:
/META-INF/batch-jobs/sampleBatch.xml
<?xml version="1.0" encoding="UTF-8"?>
<job id="sampleBatch" version="1.0" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd">
<step id="sampleStep">
<chunk item-count="100">
<reader ref="com.springapp.batch.SampleReader">
<properties>
<property name="sample" value="#{partitionPlan['sample']}"/>
</properties>
</reader>
<writer ref="com.springapp.batch.SampleWriter">
<properties>
<property name="sample" value="#{partitionPlan['sample']}"/>
</properties>
</writer>
</chunk>
<partition>
<mapper ref="com.springapp.batch.SamplePartitionMapper"/>
</partition>
</step>
</job>
com.springapp.batch.SamplePartitionMapper:
package com.springapp.batch;
import java.util.Properties;
import javax.batch.api.partition.PartitionMapper;
import javax.batch.api.partition.PartitionPlan;
import javax.batch.api.partition.PartitionPlanImpl;
public class SamplePartitionMapper implements PartitionMapper {
@Override
public PartitionPlan mapPartitions() throws Exception {
final PartitionPlan partitionPlan = new PartitionPlanImpl();
int size = 3;
Properties[] partitionProps = new Properties[size];
for (int i=0; i<size; i++) {
final Properties properties = new Properties();
properties.put("sample", ""+i);
partitionProps[i] = properties;
System.out.println("mapPartitions: " + i);
}
partitionPlan.setThreads(1);
partitionPlan.setPartitions(partitionProps.length);
partitionPlan.setPartitionProperties(partitionProps);
return partitionPlan;
}
}
com.springapp.batch.SampleReader:
public class SampleReader extends AbstractItemReader {
@Inject
@BatchProperty
private String sample;
Iterator<Integer> iter;
@Override
public void open(Serializable checkpoint) throws Exception {
System.out.println("open for reading sample: " + sample);
ArrayList list = new ArrayList<Integer>();
for(int i=0; i<Integer.parseInt(sample); i++) {
list.add(new Integer(i));
}
iter = list.iterator();
}
@Override
public Integer readItem() throws Exception {
if(iter.hasNext())
return iter.next();
else
return null;
}
}
com.springapp.batch.SampleWriter:
public class SampleWriter extends AbstractItemWriter {
@Inject
@BatchProperty
private String sample;
@Override
public void writeItems(List<Object> items) throws Exception {
System.out.println("writeItems sample: " + sample);
if(sample.equals("1")) {
throw new Exception("FAIL PARTITION 1");
}
if(sample.equals("2")) {
throw new Exception("FAIL PARTITION 2");
}
for (Object itemObj : items) {
Integer item = (Integer) itemObj;
System.out.println(item);
}
}
}
TestJob 测试运行者:
package com.springapp.batch;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.batch.operations.JobOperator;
import javax.batch.runtime.BatchRuntime;
import javax.batch.runtime.JobExecution;
import javax.inject.Inject;
import junit.framework.Assert;
import org.junit.Test;
//@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:spring-config.xml")
public class AppTests {
@Test
public void testJob() throws Exception {
JobOperator jobOperator = BatchRuntime.getJobOperator();
long jobExecution = jobOperator.start("sampleBatch", new Properties());
int attempt = 0;
while (true) {
JobExecution execution = jobOperator.getJobExecution(jobExecution);
if (execution.getEndTime() != null) {
//check status
if( "FAILED".equals(execution.getExitStatus()) && attempt < 3 ) {
attempt++;
System.out.println("Batch failed, trying to restart (attempt " + attempt + ")..");
jobExecution = jobOperator.restart(jobExecution, new Properties());
continue;
}
System.out.println("Batch ended with status: " + execution.getExitStatus());
break;
}
}
Assert.assertEquals("COMPLETED", jobOperator.getJobExecution(jobExecution).getExitStatus());
}
}
看着这个,它看起来像一个错误。我已经记录了 Jira 问题 BATCH-2364 来跟踪它。