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?
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.
@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>
...