web-dev-qa-db-ja.com

ThreadpoolExecutorを使用したSQSListener

以下の例では、最大プールサイズとコアプールサイズを1に設定していますが、メッセージは処理されていません。デバッグログを有効にすると、SQSからプルされているメッセージを確認できますが、処理/削除されていないようです。ただし、コアと最大プールのサイズを2に増やすと、メッセージが処理されているように見えます。

[〜#〜]編集[〜#〜]

Springは、キューからデータを読み取るレシーバーにスレッドを割り当てている可能性があるため、メッセージを処理しているリスナーにスレッドを割り当てることができないと思います。 corepoolsizeを2に増やすと、メッセージがキューから読み取られているのがわかりました。別のリスナーを追加したとき(デッドレターキュー用)、同じ問題が発生しました-メッセージが処理されていなかったため、2つのスレッドでは不十分でした。 corepoolsizeを3に増やすと、メッセージの処理が開始されました。この場合、キューからメッセージを読み取るために1つのスレッドが割り当てられ、2つのリスナーにそれぞれ1つのスレッドが割り当てられたと想定します。

@Configuration
public class SqsListenerConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "aws.configuration")
    public ClientConfiguration clientConfiguration() {
        return new ClientConfiguration();
    }


    @Bean
    @Primary
    public AWSCredentialsProvider awsCredentialsProvider() {

        ProfileCredentialsProvider credentialsProvider = new ProfileCredentialsProvider("credential");
        try {
            credentialsProvider.getCredentials();
            System.out.println(credentialsProvider.getCredentials().getAWSAccessKeyId());
            System.out.println(credentialsProvider.getCredentials().getAWSSecretKey());

        } catch (Exception e) {
            throw new AmazonClientException(
                    "Cannot load the credentials from the credential profiles file. " +
                            "Please make sure that your credentials file is at the correct " +
                            "location (~/.aws/credentials), and is in valid format.",
                    e);
        }
        return credentialsProvider;
    }


    @Bean
    @Primary
    public AmazonSQSAsync amazonSQSAsync() {
        return AmazonSQSAsyncClientBuilder.standard().
                withCredentials(awsCredentialsProvider()).
                withClientConfiguration(clientConfiguration()).
                build();
    }


    @Bean
    @ConfigurationProperties(prefix = "aws.queue")
    public SimpleMessageListenerContainer simpleMessageListenerContainer(AmazonSQSAsync amazonSQSAsync) {
        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
        simpleMessageListenerContainer.setAmazonSqs(amazonSQSAsync);
        simpleMessageListenerContainer.setMessageHandler(queueMessageHandler());
        simpleMessageListenerContainer.setMaxNumberOfMessages(10);
        simpleMessageListenerContainer.setTaskExecutor(threadPoolTaskExecutor());
        return simpleMessageListenerContainer;
    }


    @Bean
    public QueueMessageHandler queueMessageHandler() {
        QueueMessageHandlerFactory queueMessageHandlerFactory = new QueueMessageHandlerFactory();
        queueMessageHandlerFactory.setAmazonSqs(amazonSQSAsync());
        QueueMessageHandler queueMessageHandler = queueMessageHandlerFactory.createQueueMessageHandler();
        return queueMessageHandler;
    }


    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setThreadNamePrefix("oaoQueueExecutor");
        executor.initialize();
        return executor;
    }


    @Bean
    public QueueMessagingTemplate messagingTemplate(@Autowired AmazonSQSAsync amazonSQSAsync) {
        return new QueueMessagingTemplate(amazonSQSAsync);
    }


}

リスナー構成

    @SqsListener(value = "${oao.sqs.url}", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
    public void onMessage(String serviceData, @Header("MessageId") String messageId, @Header("ApproximateFirstReceiveTimestamp") String approximateFirstReceiveTimestamp) {

        System.out.println(" Data = " + serviceData + " MessageId = " + messageId);

        repository.execute(serviceData);
}
10
Punter Vicky

corePoolSizemaximumPoolSizeを同じに設定すると、fixed-size thread poolが作成されます。ルールの非常に良い説明が文書化されています ここ

maxPoolSizeを設定すると、タスクを暗黙的にドロップできます。ただし、デフォルトのキュー容量はInteger.MAX_VALUEであり、実用的な目的では無限大です。

注意すべき点は、ThreadPoolTaskExecutorが下にThreadPoolExecutorを使用していることです。これは、 docs で説明されているように、キューイングに対してやや変わったアプローチを採用しています。

corePoolSize以上のスレッドが実行されている場合、エグゼキュータは常に、新しいスレッドを追加するのではなく、リクエストをキューに入れることを好みます。

つまり、maxPoolSizeはキューがいっぱいの場合にのみ関係します。そうでない場合、スレッドの数がcorePoolSizeを超えることはありません。例として、完了しないタスクをスレッドプールに送信すると、次のようになります。

  • 最初のcorePoolSize送信は、それぞれ新しいスレッドを開始します。
  • その後、すべての送信はキューに入れられます。
  • キューが有限でその容量が使い果たされた場合、各送信はmaxPoolSizeまでの新しいスレッドを開始します。
  • プールとキューの両方がいっぱいになると、新しい送信は拒否されます。

キューイング-読む docs

任意のBlockingQueueを使用して、送信されたタスクを転送および保持できます。このキューの使用は、プールのサイズ設定と相互作用します。

  • 実行中のスレッドがcorePoolSize未満の場合、エグゼキューターは常にキューイングよりも新しいスレッドの追加を優先します。
  • CorePoolSize以上のスレッドが実行されている場合、Executorは常に、新しいスレッドを追加するのではなく、リクエストをキューに入れることを優先します。
  • リクエストをキューに入れることができない場合、maximumPoolSizeを超えない限り、新しいスレッドが作成されます。超えた場合、タスクは拒否されます。

Unbounded queues。無制限のキュー(たとえば、事前定義された容量のないLinkedBlockingQueue)を使用すると、すべてのcorePoolSizeスレッドがビジーの場合に新しいタスクがキューに入れられます。したがって、作成されるスレッドはcorePoolSize以下です。 (したがって、maximumPoolSizeの値は効果がありません。)

  1. スレッドの数がcorePoolSize未満の場合は、新しいスレッドを作成して新しいタスクを実行します。
  2. スレッドの数がcorePoolSizeと等しい(または多い)場合は、タスクをキューに入れます。
  3. キューがいっぱいで、スレッドの数がmaxPoolSize未満の場合は、タスクを実行するための新しいスレッドを作成します。
  4. キューがいっぱいで、スレッドの数がmaxPoolSize以上の場合、タスクを拒否します。
7