web-dev-qa-db-fra.com

Spring Boot @SqsListener - ne fonctionne pas - avec exception - TaskRejectedException

J'ai un AWS SQS avec 5000 messages déjà sur la file d'attente (l'exemple de message ressemble à ceci `` Bonjour @ 1 '') J'ai créé une application SpringBoot et à l'intérieur de l'une des classes de composants, créez une méthode pour lire les messages du SQS.

package com.example.aws.sqs.service;

import org.springframework.cloud.aws.messaging.listener.SqsMessageDeletionPolicy;
import org.springframework.cloud.aws.messaging.listener.annotation.SqsListener;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class MessageReceiverService {   

@SqsListener(value = { "${cloud.aws.sqs.url}" }, deletionPolicy = SqsMessageDeletionPolicy.ALWAYS)
public void readMessage(String message){
    log.info("Reading Message... {}", message);
}

}

Ma principale classe SpringBoot

@SpringBootApplication 
public class AwsSqsApplicationConsumer {
public static void main(String[] args) {
    SpringApplication.run(AwsSqsApplicationConsumer.class, args);
}
}

Exception que j'obtiens lorsque l'application s'exécute:

s.c.a.m.l.SimpleMessageListenerContainer : An Exception occurred while polling queue '<my sqs name>'. The failing operation will be retried in 10000 milliseconds
org.springframework.core.task.TaskRejectedException: Executor [Java.util.concurrent.ThreadPoolExecutor@7c1594a5[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 20]] did not accept task: org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$SignalExecutingRunnable@1cbd9ef2
at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.Java:309) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$AsynchronousMessageListener.run(SimpleMessageListenerContainer.Java:286) ~[spring-cloud-aws-messaging-2.0.0.RELEASE.jar:2.0.0.RELEASE]
at Java.util.concurrent.Executors$RunnableAdapter.call(Executors.Java:511) [na:1.8.0_65]
at Java.util.concurrent.FutureTask.run(FutureTask.Java:266) [na:1.8.0_65]
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1142) [na:1.8.0_65]
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:617) [na:1.8.0_65]
at Java.lang.Thread.run(Thread.Java:745) [na:1.8.0_65]
Caused by: Java.util.concurrent.RejectedExecutionException: Task org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$SignalExecutingRunnable@1cbd9ef2 rejected from Java.util.concurrent.ThreadPoolExecutor@7c1594a5[Running, pool size = 3, active threads = 2, queued tasks = 0, completed tasks = 20]
at Java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.Java:2047) ~[na:1.8.0_65]
at Java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.Java:823) [na:1.8.0_65]
at Java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.Java:1369) [na:1.8.0_65]
at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.Java:306) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
... 6 common frames omitted

Je ne configure aucun service d'exécuteur personnalisé. Utilisation des haricots de printemps préconfigurés. springBootVersion = '2.0.3.RELEASE' springCloudVersion = 'Finchley.RELEASE'

8
Hbargujar

la définition du nombre maximum de messages semble résoudre le problème:

@Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSQS){
    SimpleMessageListenerContainerFactory factory = new SimpleMessageListenerContainerFactory();
    factory.setAmazonSqs(amazonSQS);
    factory.setMaxNumberOfMessages(10);
    return factory;
}
11
Zaid Direya

Le problème vient de la configuration du thread d'écoute. Voir ce qui suit

...
ThreadPoolExecutor@7c1594a5[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 20]]
...

La taille par défaut du pool de threads est inférieure à ce que vous désirez.

Ajoutez la configuration suivante à votre application Spring

@Configuration
public class TasksConfiguration implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5); // TODO: Load this from configuration
        taskScheduler.initialize();
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
}

Maintenant, vous devriez pouvoir traiter ces tâches.

P.S. Quelles que soient les tâches qui ont été rejetées plus tôt, elles seront récupérées plus tard après une certaine période.

Edit: Je pense que les gens ont peur du nombre en ligne .setPoolSize(5000). C'est un numéro configurable, vous pouvez choisir n'importe quel numéro adapté à vos besoins. Pour la réponse, je le réduit à un plus petit nombre.

3
Ganesh Satpute

Hé, j'ai résolu ce problème en utilisant Spring Listener. Voici le code, j'espère qu'il vous aidera.

Dans la solution suivante, une fois que toutes les initialisations de beans sont terminées, un nouvel exécuteur de tâches avec une taille de pool plus grande est alloué.

@Component
public class PostBeansConstructionListener{

    @EventListener
    public void handleContextRefreshedEvent(ContextRefreshedEvent event){
        final ApplicationContext applicationContext = event.getApplicationContext();
        final SimpleMessageListenerContainer simpleMessageListenerContainer = applicationContext.getBean(SimpleMessageListenerContainer.class);
        setAsyncTaskExecutor(simpleMessageListenerContainer);
    }

    private void setAsyncTaskExecutor(SimpleMessageListenerContainer simpleMessageListenerContainer) {
        try{
            simpleMessageListenerContainer.setTaskExecutor(getAsyncExecutor());
        }catch(Exception ex){
            throw new RuntimeException("Not able to create Async Task Executor for SimpleMessageListenerContainer.", ex);
        }
    }

    public AsyncTaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("threadPoolExecutor-SimpleMessageListenerContainer-");
        executor.initialize();
        return executor;
    }
}
1
Shrikant Pandit

Je pense que c'est un bug ou une erreur au printemps. Le problème provient des valeurs par défaut de:

public class SimpleMessageListenerContainer extends AbstractMessageListenerContainer {

    private static final int DEFAULT_WORKER_THREADS = 2;

et

abstract class AbstractMessageListenerContainer implements InitializingBean, DisposableBean, SmartLifecycle, BeanNameAware {
    private static final int DEFAULT_MAX_NUMBER_OF_MESSAGES = 10;

Si aucun maxNumberOfMessages n'est défini, il utilise 10 comme nombre de messages à extraire de SQS et 2 comme nombre de travailleurs dans l'exécuteur de tâche. Cela signifie que s'il tire 3 messages ou plus à la fois, vous obtenez cette exception. Si vous définissez manuellement maxNumberOfMessages sur une valeur (n'importe quelle valeur), il l'utilisera aux deux endroits en synchronisant les valeurs comme je pense que cela est prévu:

    @Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer(
            SimpleMessageListenerContainerFactory factory, QueueMessageHandler messageHandler)
    {
        SimpleMessageListenerContainer container = factory.createSimpleMessageListenerContainer();
        container.setMaxNumberOfMessages(5);
        container.setMessageHandler(messageHandler);
        return container;
    }
0
user2273176

Impossible d'ajouter un commentaire aux réponses précédentes pour expliquer davantage pourquoi le problème se produit et pourquoi le paramètre de solution MaxNumberOfMessages des messages fonctionne. Espérons que ce qui suit aide à tout clarifier.

SimpleMessageListenerContainerThreadPoolTaskExecutor est configuré pour avoir une taille de pool de base de 2 threads, une taille de pool maximale de 3 threads et une capacité de file d'attente de 0. Cependant, le nombre maximum de messages par défaut à renvoyer sur un le sondage vers Amazon SQS est défini sur 10. Cela signifie que si 10 messages sont disponibles dans un seul sondage, il n'y aura pas assez de threads pour les traiter. Ainsi, le RejectedExecutionException est lancé.

La configuration de setMaxNumberOfMessages sur 10 sur SimpleMessageListenerContainerFactory définit la taille maximale du pool de threads sur 11, ce qui devrait permettre de disposer de suffisamment de threads. Il ne définit pas la capacité de la file d'attente.

Pour définir la capacité de la file d'attente, un TaskExecutor distinct peut être initialisé et défini sur le bean SimpleMessageListenerContainerFactory comme suit:

@Bean(name = "sqsAsyncTaskExecutor")
public AsyncTaskExecutor asyncTaskExecutor(@Value("${external.aws.sqs.core-thread-count}") int coreThreadCount,
                                           @Value("${external.aws.sqs.max-thread-count}") int maxThreadCount,
                                           @Value("${external.aws.sqs.queue-capacity}") int queueCapacity) {
    ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
    asyncTaskExecutor.setCorePoolSize(coreThreadCount);
    asyncTaskExecutor.setMaxPoolSize(maxThreadCount);
    asyncTaskExecutor.setQueueCapacity(queueCapacity);
    asyncTaskExecutor.setThreadNamePrefix("threadPoolExecutor-SimpleMessageListenerContainer-");
    asyncTaskExecutor.initialize();
    return asyncTaskExecutor;
}

@Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSQS, @Qualifier("sqsAsyncTaskExecutor") AsyncTaskExecutor asyncTaskExecutor) {
    SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory = new SimpleMessageListenerContainerFactory();
    simpleMessageListenerContainerFactory.setTaskExecutor(asyncTaskExecutor);
    return simpleMessageListenerContainerFactory;
}

Les valeurs que j'utilise étaient coreThreadCount = 5, maxThreadCount = 20, queueCapacity = 10.

Comme je l'ai déjà dit, je pense que la configuration de setMaxNumberOfMessages sur 10 sur SimpleMessageListenerContainerFactory devrait être suffisante pour traiter tous les messages en lot extraits d'une seule demande. Cependant, si vous sentez que vous avez besoin d'un contrôle plus précis sur le TaskExecutor, cette configuration fonctionne également.

0
Matt Garner