web-dev-qa-db-ja.com

Spring Boot @SqsListener-機能していません-例外あり-TaskRejectedException

キューにすでに5000メッセージのAWS SQSがあります(サンプルメッセージは「Hello @ 1」のようになります)SpringBootアプリケーションを作成し、コンポーネントクラスの1つの内部で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);
}

}

私のメインのSpringBootクラス

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

アプリケーションの実行時に私が受け取る例外:

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

カスタムExecutorサービスを構成していません。事前構成されたSpring Beanを使用する。 springBootVersion = '2.0.3.RELEASE' springCloudVersion = 'Finchley.RELEASE'

8
Hbargujar

メッセージの最大数を設定すると問題が解決するようです:

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

問題は、リスナーのスレッド構成にあります。次を参照してください

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

デフォルトのスレッドプールサイズは、希望するサイズよりも小さくなっています。

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);
    }
}
_

これで、これらのタスクを処理できるようになります。

追伸早期に却下されたタスクは、特定の期間の後に取得されます。

編集:.setPoolSize(5000)の数字に人々は怯えていると思います。これは、要件に適した任意の数を選択できる構成可能な数です。答えとして、私はそれをより小さい数に減らしています。

3
Ganesh Satpute

こんにちは、Spring Listenerを使用してこの問題を解決しました。以下はコードです、それが役に立てば幸いです。

次のソリューションでは、すべてのBeanの初期化が完了すると、より大きなプールサイズの新しいタスクエグゼキューターが割り当てられます。

@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

春のバグか見落としだと思います。この問題は、次のデフォルト値に起因します。

public class SimpleMessageListenerContainer extends AbstractMessageListenerContainer {

    private static final int DEFAULT_WORKER_THREADS = 2;

そして

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

MaxNumberOfMessagesが設定されていない場合は、SQSからプルするメッセージの数として10を使用し、タスクエグゼキューター内のワーカーの数として2を使用します。つまり、3つ以上のメッセージを一度にプルすると、その例外が発生します。 maxNumberOfMessagesを手動で値(任意の値)に設定すると、期待どおりに値の同期を両方の場所で使用します。

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

以前の回答にコメントを追加して、問題が発生している理由と、メッセージのMaxNumberOfMessages設定が機能する理由をさらに説明することはできません。うまくいけば、以下はすべてを明確にするのに役立ちます。

SimpleMessageListenerContainerThreadPoolTaskExecutorは、コアプールサイズが2スレッド、最大プールサイズが3スレッド、キュー容量が0になるように構成されています。ただし、デフォルトで返されるメッセージの最大数は、 Amazon SQSへのポーリングは10に設定されています。1回のポーリングで10個のメッセージが利用できる場合、それらを処理するのに十分なスレッドがありません。したがって、RejectedExecutionExceptionがスローされます。

setMaxNumberOfMessagesSimpleMessageListenerContainerFactoryを10に設定すると、最大スレッドプールサイズが11に設定され、十分なスレッドを使用できるようになります。キューの容量は設定されません。

キュー容量を設定するには、次のようにSimpleMessageListenerContainerFactory Beanに個別のTaskExecutorを初期化して設定できます。

@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;
}

私が使用した値は、coreThreadCount = 5、maxThreadCount = 20、queueCapacity = 10でした。

すでに述べたように、SimpleMessageListenerContainerFactoryでsetMaxNumberOfMessagesを10に設定するだけで、単一の要求からフェッチされたすべてのバッチメッセージを処理できると思います。ただし、TaskExecutorをより正確に制御する必要があると思われる場合は、この構成も機能します。

0
Matt Garner