如何使用Spring Integration处理来自AWS SQS FiFo队列的10条以上并发消息

时间:2018-09-14 16:33:21

标签: java spring-integration amazon-sqs spring-integration-dsl spring-cloud-aws

我希望能够使用Spring Integration Workflow一次处理10条以上的SQS消息。

从这个问题出发,建议使用 ExecutorChannel 。我更新了代码,但仍然有相同的症状。

How execute Spring integration flow in multiple threads to consume more Amazon SQS queue messages in parallel?

进行此更新后,我的应用程序会请求10条消息,并对它们进行处理,只有在流程结束时我调用 amazonSQSClient.deleteMessage 后,它才会接受来自SQS的另外10条消息队列。

应用程序使用SQS FiFo队列。

还有其他我想念的东西吗?还是使用 SqsMessageDeletionPolicy.NEVER 然后在流程结束时删除消息是不可避免的症状吗?由于其他限制,在流程开始时接受消息并不是真正的选择。

以下是相关的代码片段,并进行了一些简化,但我希望它能表达问题。

队列配置

@Bean
public AsyncTaskExecutor inputChannelTaskExecutor() {
    SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
    executor.setConcurrencyLimit(50);
    return executor;
}

@Bean
@Qualifier("inputChannel")
public ExecutorChannel inputChannel() {
    return new ExecutorChannel(inputChannelTaskExecutor());
}

我还尝试了ThreadPoolTask​​Executor而不是SimpleAsyncTaskExecutor,结果相同,但如果它提供其他见解,我也将其包括在内。

    @Bean
    public AsyncTaskExecutor inputChannelTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("spring-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.afterPropertiesSet();
        executor.initialize();
        return executor;
    }

SQS通道适配器

@Bean
public SqsMessageDrivenChannelAdapter changeQueueMessageAdapter() {
    SqsMessageDrivenChannelAdapter adapter = new SqsMessageDrivenChannelAdapter(this.amazonSQSClient, changeQueue);
    adapter.setOutputChannel(inputChannel);
    adapter.setMessageDeletionPolicy(SqsMessageDeletionPolicy.NEVER);
    return adapter;
}


@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerSpec poller() {
    return Pollers.fixedRate(500, TimeUnit.MILLISECONDS).maxMessagesPerPoll(10);
}

简化的主流程

对我们来说,常见的情况是在短时间内获得许多分支编辑。此流程仅“关心”至少发生了一次编辑。 messageTransformer 从有效负载文档中提取一个ID,并将其放入标头 dsp_docId 中,然后我们将其用于汇总(我们在其他一些地方使用了该ID,因此我们觉得标头有意义,而不是在自定义聚合器中完成所有工作)。

provisioningServiceActivator 检索分支的最新版本,然后路由器决定是否需要进一步的转换(在这种情况下,它将其发送到 transformBranchChannel )或被发送到我们的PI实例(通过sendToPiChannel)。

转换流程(未显示,我认为您不需要它)最终导致发送到PI流程,它首先要做更多工作。

listingGroupProcessor 捕获所有 aws_receiptHandle 标头,并将它们作为|添加到新标头中。分隔列表。

sendToPi流(和errorFlow)以对自定义处理程序的调用结束,该处理程序负责删除aws_receiptHandle字符串列表所引用的所有SQS消息。

@Bean
IntegrationFlow sqsListener() {
    return IntegrationFlows.from(inputChannel)
                           .transform(messageTransformer)
                           .aggregate(a -> a.correlationExpression("1")
                                            .outputProcessor(listingGroupProcessor)
                                            .autoStartup(true)
                                            .correlationStrategy(message -> message.getHeaders().get("dsp_docId"))
                                            .groupTimeout(messageAggregateTimeout)  // currently 25s
                                            .expireGroupsUponCompletion(true)
                                            .sendPartialResultOnExpiry(true)
                                            .get())

                           .handle(provisioningServiceActivator, "handleStandard")
                           .route(Branch.class, branch -> (branch.isSuppressed() == null || !branch.isSuppressed()),
                                  routerSpec -> routerSpec.channelMapping(true, "transformBranchChannel")
                                                          .resolutionRequired(false)
                                                          .defaultOutputToParentFlow())

                           .channel(sendtoPiChannel)
                           .get();
}

1 个答案:

答案 0 :(得分:0)

我以为我会将其发布为答案,因为这可以解决我的问题,并且可能会帮助其他人。作为答案,它更有可能被发现,而不是对可能被忽略的原始问题进行编辑。

首先,我应该注意到我们正在使用 FiFo 队列。

问题实际上是在链的更深处,我们将 MessageGroupId 设置为一个描述数据源的简单值。这意味着我们有非常大的消息组。

ReceiveMessage文档中可以看出,在这种情况下,它很明智地停止了您从该组中请求更多消息,因为如果需要将消息放回队列中,就不可能保证订单的顺序。

更新发布消息的代码以设置适当的 MessageGroupId ,然后表示 ExecutorChannel 可以按预期工作。

  

虽然具有特定MessageGroupId的消息是不可见的,但是直到可见性超时到期,才不会再返回属于同一MessageGroupId的消息。只要仍然可见,您仍然可以接收带有另一个MessageGroupId的消息。