使用Spring Boot和Project Reactor处理SQS消息

我最近参与了一个项目,在该项目中,我不得不有效地处理通过AWS SQS Queue流入的大量消息。 在这篇文章(可能还有一篇)中,我将介绍使用出色的Project Reactor处理消息的方法。

以下是我要进行的设置:

设置本地AWS环境

在我进入代码之前,让我先做一些准备。 首先,如何获得SNS和SQS的本地版本。 最简单的方法之一是使用localstack 。 我使用这里描述的docker-compose版本

我将使用的第二个实用程序是AWS CLI。 该网站包含有关如何在本地安装的详细信息。

一旦这两个实用程序都到位,快速测试应验证设置:

 # Create a queue  aws --endpoint http: //localhost:4576 sqs create-queue --queue-name test-queue  # Send a sample message  aws --endpoint http: //localhost:4576 sqs send-message --queue-url http://localhost:4576/queue/test-queue --message-body "Hello world"  # Receive the message  aws --endpoint http: //localhost:4576 sqs receive-message --queue-url http://localhost:4576/queue/test-queue 

项目反应堆的基础

Project Reactor实现了Reactive Streams规范,并提供了一种跨异步边界处理数据流的方法,该方法尊重背压。 这里有很多词,但本质上是这样想的:

1. SQS产生数据 2.应用程序将使用它并将其作为数据流进行处理 3.应用程序应以可持续的速度使用数据–不应输入太多数据。这正式称为 “背压”

AWS开发工具包2

我将用于消耗AWS SQS数据的库是
AWS开发工具包2 。 该库在幕后使用了非阻塞IO。

该库提供了拨打电话的同步版本以及异步版本。 考虑从SQS队列中获取记录的同步方式:

 import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest  import software.amazon.awssdk.services.sqs.SqsClient  val receiveMessageRequest: ReceiveMessageRequest = ReceiveMessageRequest.builder() .queueUrl(queueUrl) .maxNumberOfMessages( 5 ) .waitTimeSeconds( 10 ) .build()  val messages: List<Message> = sqsClient.receiveMessage(receiveMessageRequest).messages() 

在这里,“ software.amazon.awssdk.services.sqs.SqsClient”用于查询sqs和同步检索一批结果。 另一方面,异步结果如下所示:

 val receiveMessageRequest: ReceiveMessageRequest = ReceiveMessageRequest.builder() .queueUrl(queueUrl) .maxNumberOfMessages( 5 ) .waitTimeSeconds( 10 ) .build()  val messages: CompletableFuture<List<Message>> = sqsAsyncClient .receiveMessage(receiveMessageRequest) .thenApply { result -> result.messages() } 

现在,输出为“ CompletableFuture”

无限循环,无背压

我最初创建消息流( Flux )的尝试非常简单–一个无限循环,它轮询AWS sqs并使用“ Flux.create”运算符从中创建Flux ,方法是:

 fun listen(): Flux<Pair<String, () -> Unit>> { return Flux.create { sink: FluxSink<List<Message>> -> while (running) { try { val receiveMessageRequest: ReceiveMessageRequest = ReceiveMessageRequest.builder() .queueUrl(queueUrl) .maxNumberOfMessages( 5 ) .waitTimeSeconds( 10 ) .build() val messages: List<Message> = sqsClient.receiveMessage(receiveMessageRequest).messages() LOGGER.info( "Received: $messages" ) sink.next(messages) } catch (e: InterruptedException) { LOGGER.error(e.message, e) } catch (e: Exception) { LOGGER.error(e.message, e) } } } .flatMapIterable(Function.identity()) .doOnError { t: Throwable -> LOGGER.error(t.message, t) } .retry() .map { snsMessage: Message -> val snsMessageBody: String = snsMessage.body() val snsNotification: SnsNotification = readSnsNotification(snsMessageBody) snsNotification.message to { deleteQueueMessage(snsMessage.receiptHandle(), queueUrl) } }  } 

它的工作方式是存在一个无限循环,该循环使用long-polling检查新消息。 消息可能并非在每次轮询时都可用,在这种情况下,会将空列表添加到流中。

然后,使用“ flatMapIterable”运算符将此列表中的最多5条消息映射到单个消息流,并通过从SNS包装器中提取消息来进一步映射(当消息从SNS转发到SQS时,SNS将包装器添加到消息),并在消息成功处理后删除消息的方法(deleteHandle)作为对返回。

这种方法可以很好地工作……但是,请想象一下有大量消息进入的情况,因为循环并没有真正意识到下游的吞吐量,它将继续将数据泵送到流中。 中间操作员的默认行为是根据最终使用者使用数据的方式来缓冲流入的数据。 由于此缓冲区是无界的,因此系统可能会达到不可持续的状态。

背压感知流

解决方法是使用其他运算符生成数据流–
助焊剂
使用此运算符的代码如下所示:

 fun listen(): Flux<Pair<String, () -> Unit>> { return Flux.generate { sink: SynchronousSink<List<Message>> -> val receiveMessageRequest: ReceiveMessageRequest = ReceiveMessageRequest.builder() .queueUrl(queueUrl) .maxNumberOfMessages( 5 ) .waitTimeSeconds( 10 ) .build() val messages: List<Message> = sqsClient.receiveMessage(receiveMessageRequest).messages() LOGGER.info( "Received: $messages" ) sink.next(messages) } .flatMapIterable(Function.identity()) .doOnError { t: Throwable -> LOGGER.error(t.message, t) } .retry() .map { snsMessage: Message -> val snsMessageBody: String = snsMessage.body() val snsNotification: SnsNotification = readSnsNotification(snsMessageBody) snsNotification.message to { deleteQueueMessage(snsMessage.receiptHandle(), queueUrl) } }  } 

这种工作方式是重复调用传递给“ Flux.generate”运算符的块–与while循环类似,在每个循环中,期望将一项添加到流中。 在这种情况下,添加到流中的项目恰好是一个列表,该列表像以前一样分解为单独的消息。

背压在这种情况下如何工作–

因此,请再次考虑下游使用者处理速度比生成端慢的情况。 在这种情况下,Flux本身将以调用generate运算符的速率减慢速度,因此要考虑下游系统的吞吐量。

结论

这应该建立一个良好的管道来处理来自SQS的消息,对此有更多细微差别,可以稍后在流中并行处理消息,我将在以后的文章中介绍。

这个例子的代码库可以在我的github仓库中找到
在这里 – https://github.com/bijukunjummen/boot-with-sns-sqs。 该代码具有完整的管道,其中包括处理消息并在处理后将其删除。

翻译自: https://www.javacodegeeks.com/2020/03/processing-sqs-messages-using-spring-boot-and-project-reactor.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/339740.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java commons lang 随机数_Apache Common-lang组件里随机数工具类RandomStringUtils的一个bug...

现在本文也转到了我自己的博客上&#xff0c;地址&#xff1a;月城小馆Apache Common组件是java开发中常用的工具&#xff0c;其中的common-lang包是java基本数据类型的处理工具&#xff0c;包括数字、字符串、日期时间等多种工具类。在org.apache.commons.lang包中有一个随机数…

初级测试开发面试题_初级开发人员在编写单元测试时常犯的错误

初级测试开发面试题自从我编写第一个单元测试以来已经有10年了。 从那时起&#xff0c;我不记得我已经编写了成千上万的单元测试。 老实说&#xff0c;我在源代码和测试代码之间没有任何区别。 对我来说是同一回事。 测试代码是源代码的一部分。 在过去的3-4年中&#xff0c;我…

java文件读写详细介绍_java文件读写操作大全

一.获得控制台用户输入的信息public String getInputMessage() throws IOException...{System.out.println("请输入您的命令∶");byte buffer[]new byte[1024];int countSystem.in.read(buffer);char[] chnew char[count-2];//最后两位为结束符&#xff0c;删去不要f…

使用SoapUI调用安全WCF SOAP服务–第1部分,该服务

在这个由三部分组成的传奇中&#xff0c;我将演示如何使用SoapUI API工具来调用安全的SOAP服务。 首先&#xff0c;我将专注于创建服务&#xff0c;在接下来的文章中它将充当被测系统。 使用基本身份验证传输安全性机制维护对该服务中资源的访问。 Windows Communication Foun…

java简单系统_Java简单学生管理系统

Java简单学生管理系统这个不需要手动输入&#xff0c;笔记记录//studentpublic class student(){private String id;//学号private String name;//姓名private int age;//年龄public String getId() {return id;}public void setId(String id) {this.id id;}public String get…

github和maven_在github上托管Maven存储库(包含源代码和javadoc)

github和maven如何通过maven使其他开发人员可以使用小型开源库&#xff1f; 一种方法是将其部署在Maven Central Repository上 。 我想要做的是将其部署到github &#xff0c;因此我可以自由地对其进行修改。 这篇文章将告诉您如何做到这一点。 我将工件部署到github的典型方法…

kafka java编程demo_Kafka简单客户端编程实例

今天&#xff0c;我们给大家带来一篇如何利用Kafka的API进行客户端编程的文章&#xff0c;这篇文章很简单&#xff0c;就是利用Kafka的API创建一个生产者和消费者&#xff0c;生产者不断向Kafka写入消息&#xff0c;消费者则不断消费Kafka的消息。下面是具体的实例代码。一、创…

java我的世界极限生存_我的世界 1.7.10 极限生存整合包

整合包介绍&#xff1a;最近总有人觉得Minecraft很无聊&#xff0c;没有什么可玩的&#xff0c;或者觉得生存太简单 那么就来试试这个吧&#xff0c;全部是增强怪物的MOD&#xff0c;保证不无聊&#xff0c;保证不简单 基本上没有增加一些新的东西&#xff0c;只增加了几种怪物…

具有InlfuxDB的Spring Boot和Micrometer第1部分:基础项目

对于那些关注此博客的人来说&#xff0c;难怪我倾向于大量使用InfluxDB。 我喜欢这样一个事实&#xff0c;它是一个真正的单一用途的数据库&#xff08;时间序列&#xff09;&#xff0c;具有许多功能&#xff0c;并且还带有企业支持。 Spring也是我选择的工具之一。 因此&…

Gradle善良:仅添加包装用于战争

我的同事Tom Wetjens 在Maven中撰写了博客文章仅打包依赖项 。 当我们想在WAR文件中包含依赖项时&#xff0c;他展示了一种Maven解决方案&#xff0c;而在其他任何作用域中都没有使用。 在这篇博客中&#xff0c;我们将看到我们如何在Gradle中解决这个问题。 假设我们在项目中…

java递归api_javaAPI_IO流基础_递归使用

IO流_递归1.递归概述递归指的是方法定义中调用自身方法的情况。2.递归的注意事项(1).要有出口&#xff0c;否则就是死递归(2).次数不能太多&#xff0c;否则就内存溢出(3).构造方法不能递归使用[不然在创建对象的时候就会内存溢出]3.递归解决问题的思想(1).分解法:把问题细分为…

PIT,JUnit 5和Gradle –仅需额外的一行配置

在Gradle&#xff08;带有gradle-pitest-plugin 1.4.7&#xff09;中发现简单&#xff0c;经过改进的PIT和JUnit 5配置。 不可否认&#xff0c;如今JUnit 5越来越受欢迎。 虽然为JUnit 5提供了一个专用于PIT的插件&#xff0c;并且gradle-pitest-plugin支持了很多年&#xff0…

apache camel_使用WildFly 8在Java EE7中自举Apache Camel

apache camel从Camel版本2.10开始&#xff0c;支持CDI&#xff08;JSR-299&#xff09;和DI&#xff08;JSR-330&#xff09;。 这为在Java EE容器中以及在独立的Java SE或CDI容器中开发和部署Apache Camel项目提供了新的机会。 是时候尝试一下并熟悉它了。 骆驼到底是什么&am…

python中可变参数怎么传递的呢_在python中,你可以在命名参数后传递可变参数吗?...

can you pass variadic arguments after named parameters?Python 3.4.3&#xff1a;答案是肯定的.如果要调用仅命名固定参数的函数,请将可变参数放在函数定义中def function(*args, bob, sally):print(args, bob, sally)values [1, 2, 3, 4]function(bob"Hi bob",…

Hibernate中保存与持久性以及saveOrUpdate之间的区别

保存与保存或更新与持久保存在Hibernate中 save和saveOrUpdate之间的区别是什么或save和persist之间的区别是任何Hibernate面试中常见的面试问题&#xff0c;就像Hibernate中get和load方法之间的区别一样。 Hibernate Session类提供了几种方法&#xff0c;可以通过诸如save&am…

java的log计算_Java普通对数(log)计算方法

Java给我提供的数学计算的工具类Math计算对数的函数有两个&#xff1a;/*** Returns the natural logarithm (base e) of a {code double}* value. Special cases:* If the argument is NaN or less than zero, then the result* is NaN.* If the argument is positive infinit…

javaee编程题_在JavaEE中使用CDI的简单面向方面的编程(AOP)

javaee编程题我们编写满足特定业务逻辑的服务API。 涵盖所有服务API&#xff08;如安全性&#xff0c;日志记录&#xff0c;审核&#xff0c;度量延迟等&#xff09;的跨领域问题很少。 这是一个重复的非业务代码&#xff0c;可以在其他方法之间重用。 重用的一种方法是将这些重…

java中的语句有哪些_java中的循环语句有哪些

Java中有三种主要的循环结构&#xff1a;while 循环do…while 循环for 循环顺序结构的程序语句只能被执行一次。如果您想要同样的操作执行多次,&#xff0c;就需要使用循环结构。一、while循环语法&#xff1a;while( 布尔表达式 ) {     //循环内容   }只要符合布尔表达…

使用自动伸缩组在AWS中运行安全数据库集群

当您必须在AWS上运行可伸缩应用程序时&#xff0c;您的数据库也必须可伸缩。 扩展无状态应用程序层更容易&#xff0c;在无状态应用程序层中&#xff0c;每个节点大部分都是可使用的-即使3节点集群中的一个节点发生故障&#xff0c;您也可以启动另一个节点&#xff0c;而没人注…

php无法新数据类型,新手入门PHP必知的七种数据类型

想要入门PHP&#xff0c;首先要学会搭建环境&#xff0c;其次是学习基础语法。PHP的基础包括数据类型&#xff0c;运算符&#xff0c;变量和常量等。在这篇文章中&#xff0c;我们主要了解什么是数据类型。数据类型是指同种数据的一个统称&#xff0c;一般会描述为XX数据类型。…