パーティショナーを使用したSpring Batch

Partitionerを使用したSpring Batch

1. 概要

以前のintroduction to Spring Batchでは、バッチ処理ツールとしてフレームワークを導入しました。 また、構成の詳細と、シングルスレッド、シングルプロセスのジョブ実行の実装についても調査しました。

いくつかの並列処理でジョブを実装するために、さまざまなオプションが提供されています。 より高いレベルでは、並列処理には2つのモードがあります。

  1. シングルプロセス、マルチスレッド

  2. マルチプロセス

この簡単な記事では、Stepのパーティショニングについて説明します。これは、シングルプロセスジョブとマルチプロセスジョブの両方に実装できます。

2. ステップの分割

パーティショニングを備えたSpringBatchは、Stepの実行を分割する機能を提供します。

image

上の図は、パーティション化されたStepを使用したJobの実装を示しています。

「マスター」と呼ばれるStepがあり、その実行はいくつかの「スレーブ」ステップに分割されています。 これらのスレーブはマスターの代わりになり、結果は変わりません。 マスターとスレーブの両方がStepのインスタンスです。 スレーブは、リモートサービスまたはローカルで実行されるスレッドになります。

必要に応じて、マスターからスレーブにデータを渡すことができます。 メタデータ(つまり、 JobRepository)は、すべてのスレーブがJob.の1回の実行で1回だけ実行されるようにします。

これがすべての仕組みを示すシーケンス図です:

image

示されているように、PartitionStepが実行を推進しています。 PartitionHandlerは、「マスター」の作業を「スレーブ」に分割する責任があります。 右端のStepはスレーブです。

3. MavenPOM

Mavenの依存関係は、前のarticleで説明したものと同じです。 つまり、Spring Core、Spring Batch、およびデータベースの依存関係(この場合はSQLite)です。

4. 設定

紹介のarticleで、いくつかの財務データをCSVからXMLファイルに変換する例を見ました。 同じ例を拡張してみましょう。

ここでは、マルチスレッド実装を使用して、財務情報を5つのCSVファイルから対応するXMLファイルに変換します。

これは、単一のJobおよびStepパーティショニングを使用して実現できます。 CSVファイルごとに1つずつ、合計5つのスレッドがあります。

まず、ジョブを作成しましょう:

@Bean(name = "partitionerJob")
public Job partitionerJob()
  throws UnexpectedInputException, MalformedURLException, ParseException {
    return jobs.get("partitioningJob")
      .start(partitionStep())
      .build();
}

ご覧のとおり、このJobPartitioningStepで始まります。 これは、さまざまなスレーブステップに分割されるマスターステップです。

@Bean
public Step partitionStep()
  throws UnexpectedInputException, MalformedURLException, ParseException {
    return steps.get("partitionStep")
      .partitioner("slaveStep", partitioner())
      .step(slaveStep())
      .taskExecutor(taskExecutor())
      .build();
}

ここでは、PartitioningStep using the StepBuilderFactoryを作成します。 そのためには、SlaveStepsPartitionerに関する情報を提供する必要があります。

Partitionerは、各スレーブの入力値のセットを定義する機能を提供するインターフェイスです。 つまり、タスクをそれぞれのスレッドに分割するロジックがここにあります。

CustomMultiResourcePartitionerと呼ばれる実装を作成しましょう。ここでは、入力ファイル名と出力ファイル名をExecutionContextに入れて、すべてのスレーブステップに渡します。

public class CustomMultiResourcePartitioner implements Partitioner {

    @Override
    public Map partition(int gridSize) {
        Map map = new HashMap<>(gridSize);
        int i = 0, k = 1;
        for (Resource resource : resources) {
            ExecutionContext context = new ExecutionContext();
            Assert.state(resource.exists(), "Resource does not exist: "
              + resource);
            context.putString(keyName, resource.getFilename());
            context.putString("opFileName", "output"+k+++".xml");
            map.put(PARTITION_KEY + i, context);
            i++;
        }
        return map;
    }
}

また、このクラスのBeanを作成し、入力ファイルのソースディレクトリを指定します。

@Bean
public CustomMultiResourcePartitioner partitioner() {
    CustomMultiResourcePartitioner partitioner
      = new CustomMultiResourcePartitioner();
    Resource[] resources;
    try {
        resources = resoursePatternResolver
          .getResources("file:src/main/resources/input/*.csv");
    } catch (IOException e) {
        throw new RuntimeException("I/O problems when resolving"
          + " the input file pattern.", e);
    }
    partitioner.setResources(resources);
    return partitioner;
}

リーダーとライターの他のステップと同様に、スレーブステップを定義します。 リーダーとライターは、StepExecutionContext.からファイル名パラメーターを受け取ることを除いて、紹介の例で見たものと同じです。

これらのBeanは、すべてのステップでstepExecutionContextパラメーターを受信できるように、ステップスコープを設定する必要があることに注意してください。 ステップスコープが設定されていない場合、Beanは最初に作成され、ステップレベルでファイル名を受け入れません。

@StepScope
@Bean
public FlatFileItemReader itemReader(
  @Value("#{stepExecutionContext[fileName]}") String filename)
  throws UnexpectedInputException, ParseException {

    FlatFileItemReader reader
      = new FlatFileItemReader<>();
    DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
    String[] tokens
      = {"username", "userid", "transactiondate", "amount"};
    tokenizer.setNames(tokens);
    reader.setResource(new ClassPathResource("input/" + filename));
    DefaultLineMapper lineMapper
      = new DefaultLineMapper<>();
    lineMapper.setLineTokenizer(tokenizer);
    lineMapper.setFieldSetMapper(new RecordFieldSetMapper());
    reader.setLinesToSkip(1);
    reader.setLineMapper(lineMapper);
    return reader;
}
@Bean
@StepScope
public ItemWriter itemWriter(Marshaller marshaller,
  @Value("#{stepExecutionContext[opFileName]}") String filename)
  throws MalformedURLException {
    StaxEventItemWriter itemWriter
      = new StaxEventItemWriter();
    itemWriter.setMarshaller(marshaller);
    itemWriter.setRootTagName("transactionRecord");
    itemWriter.setResource(new ClassPathResource("xml/" + filename));
    return itemWriter;
}

スレーブステップでリーダーとライターについて言及している間、これらのファイル名はstepExecutionContextからファイル名を受け取るため、使用されないため、引数をnullとして渡すことができます。

@Bean
public Step slaveStep()
  throws UnexpectedInputException, MalformedURLException, ParseException {
    return steps.get("slaveStep").chunk(1)
      .reader(itemReader(null))
      .writer(itemWriter(marshaller(), null))
      .build();
}

5. 結論

このチュートリアルでは、Spring Batchを使用して並列処理でジョブを実装する方法について説明しました。

いつものように、この例の完全な実装はover on GitHubで利用できます。