web-dev-qa-db-fra.com

Configuration du processeur asynchrone Spring Batch pour des performances optimales

J'ai un problème avec la création d'un processeur asynchrone dans Spring Batch . Mon processeur reçoit ID de reader et crée un objet en fonction de la réponse de l'appel SOAP. Parfois, pour 1 entrée (ID), il doit y avoir par exemple 60-100 SOAP appels et parfois juste un. J'ai essayé de créer une étape multithread, par exemple, elle traitait 50 entrées à la fois, mais cela ne servait à rien car 49 threads faisaient leur travail en 1 seconde et étaient bloqués, en attente de celle qui faisait 60 100 appels SOAP. Maintenant, j'utilise AsyncItemProcessor + AsyncItemWriter mais cette solution fonctionne lentement pour moi. Comme mon entrée (IDs) est grande, environ 25 000 éléments lus à partir de la base de données, j'aimerais démarrer environ 50 à 100 entrées à la fois.

Voici ma configuration:

@Configuration
public class BatchConfig {

    @Autowired
    public JobBuilderFactory jobBuilderFactory;
    @Autowired
    public StepBuilderFactory stepBuilderFactory;
    @Autowired
    private DatabaseConfig databaseConfig;
    @Value(value = "classpath:Categories.txt")
    private Resource categories;

    @Bean
    public Job processJob() throws Exception {
        return jobBuilderFactory.get("processJob").incrementer(new RunIdIncrementer()).listener(listener()).flow(orderStep1()).end().build();
    }

    @Bean
    public Step orderStep1() throws Exception {
        return stepBuilderFactory.get("orderStep1").<Category, CategoryDailyResult>chunk(1).reader(reader()).processor(asyncItemProcessor()).writer(asyncItemWriter()).taskExecutor(taskExecutor()).build();
    }

    @Bean
    public JobExecutionListener listener() {
        return new JobCompletionListener();
    }


    @Bean
    public ItemWriter asyncItemWriter() {
        AsyncItemWriter<CategoryDailyResult> asyncItemWriter = new AsyncItemWriter<>();
        asyncItemWriter.setDelegate(itemWriter());
        return asyncItemWriter;
    }

    @Bean
    public ItemWriter<CategoryDailyResult> itemWriter(){
        return new Writer();
    }

    @Bean
    public ItemProcessor asyncItemProcessor() {
        AsyncItemProcessor<Category, CategoryDailyResult> asyncItemProcessor = new AsyncItemProcessor<>();
        asyncItemProcessor.setDelegate(itemProcessor());
        asyncItemProcessor.setTaskExecutor(taskExecutor());
        return asyncItemProcessor;
    }

    @Bean
    public ItemProcessor<Category, CategoryDailyResult> itemProcessor(){
        return new Processor();
    }

    @Bean
    public TaskExecutor taskExecutor(){
        SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
        taskExecutor.setConcurrencyLimit(50);
        return taskExecutor;
    }

    @Bean(destroyMethod = "")
    public ItemReader<Category> reader() throws Exception {
        String query = "select c from Category c where not exists elements(c.children)";

        JpaPagingItemReader<Category> reader = new JpaPagingItemReader<>();
        reader.setSaveState(false);
        reader.setQueryString(query);
        reader.setEntityManagerFactory(databaseConfig.entityManagerFactory().getObject());
        reader.setPageSize(1);

        return reader;
    }
}

Comment puis-je booster ma candidature? Peut-être que je fais quelque chose de mal? Tous les commentaires sont les bienvenus;)

@Edit: Pour la saisie des ID: 1 à 100, je veux par exemple 50 threads qui exécutent un processeur. Je veux qu’ils ne se bloquent pas: L’entrée de traitement de Thread1 "1" pendant 2 minutes et à ce moment je veux que Thread2 traite l’entrée "2", "8", "64" qui sont petites et s’exécutent en quelques secondes .

@ Edit2: Mon objectif: J'ai 25 000 identifiants dans la base de données, je les lis avec JpaPagingItemReader et chaque identifiant est traité par le processeur. Chaque article est indépendant l'un de l'autre. Pour chaque ID, je fais un appel SOAP de 0 à 100 fois en boucle, puis je crée un objet que je passe à Writer et que je sauvegarde dans une base de données. Comment puis-je obtenir les meilleures performances pour une telle tâche?

14
crooked

Vous devriez partitionner votre travail. Ajoutez une étape partitionnée comme ceci:

@Bean
public Step partitionedOrderStep1(Step orderStep1) {
    return stepBuilder.get("partitionedOrderStep1")
            .partitioner(orderStep1)
            .partitioner("orderStep1", new SimplePartitioner())
            .taskExecutor(taskExecutor())
            .gridSize(10)  //Number of concurrent partitions
            .build();
}

Ensuite, utilisez cette étape dans votre définition de travail. L'appel .gridSize () configure le nombre de partitions à exécuter simultanément. Si l'un de vos objets Reader, Processor ou Writer a un état, vous devez l'annoter avec @StepScope.

1
Joe Chiavaroli

@KCrookedHand: J'ai traité genre de scénario similaire , je devais lire quelques milliers de personnes et je devais appeler le service SOAP (je l'ai injecté dans itemReader) pour les critères de correspondance. 

Ma configuration ressemble à celle présentée ci-dessous. En gros, vous disposez de plusieurs options pour réaliser un traitement parallèle. Deux d’entre elles sont l’approche «partitionnement» et l’approche «client/serveur». J'ai choisi le partitionnement car j'aurai plus de contrôle sur le nombre de partitions dont j'ai besoin en fonction de mes données.

S'il vous plaît, ThreadPoolTaskExecutor comme indiqué par @MichaelMinella, pour l'exécution en dessous de l'étape en utilisant la tasklet où elle s'applique.

<batch:step id="notificationMapper">
            <batch:partition partitioner="partitioner"
                step="readXXXStep" />
        </batch:step>
    </batch:job>


    <batch:step id="readXXXStep">
        <batch:job ref="jobRef" job-launcher="jobLauncher"
            job-parameters-extractor="jobParameterExtractor" />
    </batch:step>

    <batch:job id="jobRef">

        <batch:step id="dummyStep" next="skippedItemsDecision">
            <batch:tasklet ref="dummyTasklet"/>
            <batch:listeners>
                <batch:listener ref="stepListener" />
            </batch:listeners>
        </batch:step>

        <batch:step id="xxx.readItems" next="xxx.then.finish">
            <batch:tasklet>
                <batch:chunk reader="xxxChunkReader" processor="chunkProcessor"
                    writer="itemWriter" commit-interval="100">
                </batch:chunk>
            </batch:tasklet>
            <batch:listeners>
                <batch:listener ref="taskletListener" />
            </batch:listeners>
        </batch:step>

        ...
0
Ashok Gudise