使用AWS和LocalStack的Spring Cloud Messaging
简介
随着对云服务需求的不断增长,Spring为整合云供应商和相关服务提供了惊人的支持。Spring Cloud for Amazon Web Services就是这样一个项目,它使我们能够使用熟悉的Spring API轻松地与AWS服务集成。
在这篇文章中,我们将研究一个简单的应用程序,它使用亚马逊SNS和SQS作为消息生产者和消费者。除此之外,我们不会创建AWS账户或直接使用AWS服务。相反,我们将使用LocalStack,它将允许我们在本地创建AWS资源。
示例应用程序可以在这里找到。
先决条件
- 对AWS、AWS CLI以及Amazon SQS等相关服务的基本知识。
- Java 11和Spring Boot的基本知识
2.4.7. - Docker和Docker Compose的设置。
亚马逊SNS和SQS介绍
亚马逊SNS
亚马逊SNS是简单通知服务的首字母缩写。它为开发人员提供了高度可扩展的、具有成本效益的和灵活的能力,可以从一个应用程序发布消息,并将其发送给其他应用程序。它遵循pub-sub架构,将消息从发布者传递给订阅者。它是一个高度解耦的服务,可以链接到各种来源。它可以用来发布电子邮件、向SQS发送消息、短信等。
发布者(如CloudWatch警报、S3事件、SNS、微服务)在一个主题上发布消息,然后发布到该特定主题的所有订阅者(网络服务器、电子邮件地址、Amazon SQS队列、AWS Lambda)。
亚马逊SQS
亚马逊SQS是Simple Queue Service的缩写,这个名字传达了很多信息,因为它是AWS的一个完全管理的服务,所以使用起来真的很简单。它遵循类似的消息传递语义,即生产者将消息放在队列中,消费者从中读取。
一旦被消费,该消息就必须从队列中删除。删除是很重要的,因为SQS假设处理可能失败。为了防止这种情况,在消费者收到消息后,它将从队列中隐藏一段时间,之后,如果它没有被删除,消息将再次出现在队列中。
SNS和SQS都是完全管理的轻量级、易于使用的API。您可以使用亚马逊SQS和SNS来解耦和扩展微服务、分布式系统和无服务器应用程序,并提高可靠性。
LocalStack简介
LocalStack是真正的AWS服务的一个开源模拟。它在我们的本地机器上提供了一个测试环境,其API与真正的AWS服务相同。我们只在集成环境和其他环境中切换到使用真正的AWS服务。使用LocalStack有很多理由,其中有几个理由比其他理由更重要,比如说:
- 能够在不与AWS互动的情况下使用AWS服务。所有的开发人员都喜欢弄脏自己的手,还有什么比尝试更好的方式来学习呢。Localstack允许你玩S3、SQS、DynamoDB、SNS等服务,这样的例子不胜枚举。
- 在本地开发环境中测试应用程序的能力。你可以使用LocalStack为你的应用程序编写集成测试,从而降低你的成本,增加对你的代码的信心。
与LocalStack相连的Spring云信息传递应用
Spring Cloud AWS提供了Amazon SQS和Amazon SNS集成,简化了消息的发布和消费。它减少了大量的模板代码,为配置和SNS和SQS做了大量的工作。让我们来设置一个支持SNS和SQS的Spring Boot项目。
设置Spring Boot应用程序
让我们首先在Spring boot Initializr的帮助下创建一个Spring Boot项目,然后在我们喜欢的IDE中打开该项目。选择Maven项目和Java语言。最后但并非最不重要的是,选择Spring boot版本 2.4.7.填入项目元数据,然后点击生成。
对于Spring Cloud,我们需要在我们的文件中配置Spring Cloud AWS BOM。 pom.xml文件中使用这个依赖性管理模块。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>2.3.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
为了增加对消息传递的支持,我们需要在Maven配置中加入Spring Cloud AWS Messaging的模块依赖。我们通过添加启动模块来做到这一点 spring-cloud-starter-aws-messaging:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-starter-aws-messaging</artifactId>
</dependency>
这些是Spring Cloud AWS所需的依赖项。接下来,我们添加一些其他依赖项,如Spring boot starter web和Lombok。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
这就完成了我们对Spring Boot项目的设置过程。
让我们开始添加SNS和SQS配置并开始发布我们的第一条消息。
亚马逊SNS配置:
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClientBuilder;
import io.awspring.cloud.messaging.core.NotificationMessagingTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import static com.authga.springcloudaws.config.AWSConfigConstants.ACCESS_KEY;
import static com.authga.springcloudaws.config.AWSConfigConstants.SECRET_KEY;
@Configuration
public class SnsConfig {
@Bean
public NotificationMessagingTemplate notificationMessagingTemplate(AmazonSNS amazonSNS) {
return new NotificationMessagingTemplate(amazonSNS);
}
@Bean
@Primary
public AmazonSNS amazonSNS(final AwsClientBuilder.EndpointConfiguration endpointConfiguration) {
BasicAWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY);
return AmazonSNSClientBuilder
.standard()
.withEndpointConfiguration(endpointConfiguration)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
}
}
在这里,我们创建了一个简单的AmazonSNSClient ,有一个自定义的端点和凭证。Spring有一个自动配置的AmazonSNSClient ,但这需要有效的AWS凭证和实际资源。我们希望使用LocalStack在本地使用AWS资源来运行我们的服务。稍后会有更多关于这个的内容。让我们来完成这个设置。
亚马逊SQS配置:
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder;
import io.awspring.cloud.messaging.config.QueueMessageHandlerFactory;
import io.awspring.cloud.messaging.support.NotificationMessageArgumentResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import java.util.List;
import static com.authga.springcloudaws.config.AWSConfigConstants.*;
@Configuration
public class SqsConfig {
@Bean
public AwsClientBuilder.EndpointConfiguration endpointConfiguration() {
return new AwsClientBuilder.EndpointConfiguration(ENDPOINT, EU_CENTRAL_1);
}
@Bean
@Primary
public AmazonSQSAsync amazonSQSAsync(final AwsClientBuilder.EndpointConfiguration endpointConfiguration) {
BasicAWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY);
return AmazonSQSAsyncClientBuilder
.standard()
.withEndpointConfiguration(endpointConfiguration)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
}
@Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory(MessageConverter messageConverter) {
var factory = new QueueMessageHandlerFactory();
factory.setArgumentResolvers(List.of(new NotificationMessageArgumentResolver(messageConverter)));
return factory;
}
@Bean
protected MessageConverter messageConverter() {
var converter = new MappingJackson2MessageConverter();
converter.setSerializedPayloadClass(String.class);
converter.setStrictContentTypeMatch(false);
return converter;
}
}
在上面的代码中,我们做了以下工作:
- 类似于
AmazonSNSClient,我们用一个自定义的端点和凭证创建了AmazonSQSClient。 - 设置
QueueMessageHandlerFactory,这样它就可以将从SQS传入的消息作为字符串转换为我们想要的实际对象,在这种情况下,Event,使用MessageConverter。 - 转换器将负责把
String消息转换为实际的Event对象。
下面描述了Event 的模型。我正在使用Lombok来减少模板代码。
Event.java
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Event implements Comparable<Event> {
private String eventId;
private String version;
private String occurredAt;
private EventData data;
@Override
public int compareTo(Event o) {
int otherVersion = Integer.parseInt(o.getVersion());
int thisVersion = Integer.parseInt(this.getVersion());
return Integer.compare(thisVersion, otherVersion);
}
}
EventData.java
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class EventData {
private String orderId;
private String owner;
private EventType eventType;
}
EventType.java
import java.io.Serializable;
public enum EventType implements Serializable {
ORDER_CREATED, ORDER_CANCELLED
}
生产者代码:
import com.authga.springcloudaws.model.Event;
import io.awspring.cloud.messaging.core.NotificationMessagingTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import static com.authga.springcloudaws.config.AWSConfigConstants.ORDER_CREATED_TOPIC;
@Slf4j
@Service
public class SimpleMessageProducer {
@Autowired
private NotificationMessagingTemplate notificationMessagingTemplate;
public void publish(Event event) {
notificationMessagingTemplate.convertAndSend(ORDER_CREATED_TOPIC, event);
}
}
- 我们使用上面的配置中创建的
NotificationMessagingTemplate。它为我们提供了几种在SNS主题上发送消息的方法。我们将使用默认的convertAndSend()方法,该方法使用我们在SqsConfig类中提供的MessageConverter,负责将我们的对象转换为消息。
消费者代码:
import com.authga.springcloudaws.model.Event;
import io.awspring.cloud.messaging.config.annotation.NotificationMessage;
import io.awspring.cloud.messaging.listener.SqsMessageDeletionPolicy;
import io.awspring.cloud.messaging.listener.annotation.SqsListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import static com.authga.springcloudaws.config.AWSConfigConstants.ORDER_QUEUE;
@Slf4j
@Controller
public class SimpleMessageConsumer implements MessageConsumer {
@Override
@SqsListener(value = ORDER_QUEUE, deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
public void consume(@NotificationMessage Event event) {
if (event != null) {
log.info("Received order event for consumer 1: " + event);
}
}
}
- 我们只需要
@SqsListener注解,它可以自动使我们的消费方法从队列中获取消息。 - 我们还添加了
@NotificationMessage注解,以便正确地反序列化并从SNS消息中提取我们的事件,因为SNS消息被包裹在一个Message对象中。
设置LocalStack并创建资源
设置LocalStack非常简单,你只需要使用下面的docker-compose文件,它就会在本地启动SNS和SQS服务。将 docker compose.yml到版本库的根目录。启动LocalStack,使用。 docker-compose up.
version: '3.0'
services:
localstack:
image: localstack/localstack:latest
environment:
- AWS_DEFAULT_REGION=eu-central-1
- EDGE_PORT=4566
- SERVICES=sqs,sns
ports:
- '4566:4566'
一旦它启动并运行,我们就可以创建所需的AWS资源。我们将创建一个SNS主题,两个SQS队列将被监听。发布和监听的代码很简单。转到你的命令行,例如终端,并执行以下命令。
创建主题。
aws --endpoint-url=http://localhost:4566 sns create-topic --name order-created-topic
创建队列:
aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name order-queue
aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name order-queue-2
将队列订阅给主题:
aws --endpoint-url=http://localhost:4566 sns subscribe
--topic-arn arn:aws:sns:eu-central-1:000000000000:order-created-topic
--protocol sqs
--notification-endpoint arn:aws:sqs:eu-central-1:000000000000:order-queue
aws --endpoint-url=http://localhost:4566 sns subscribe
--topic-arn arn:aws:sns:eu-central-1:000000000000:order-created-topic
--protocol sqs
--notification-endpoint arn:aws:sqs:eu-central-1:000000000000:order-queue-2
验证你可以列出队列和订阅:
aws --endpoint-url=http://localhost:4566 sqs list-queues
aws --endpoint-url=http://localhost:4566 sns list-subscriptions
这就是了!
验证事件的发送和接收
运行Spring Boot应用程序:
- 克隆回购中的示例代码。转到根目录。
- 使用Maven插件,使用项目目录下的命令运行Spring Boot应用程序。
mvn spring-boot:run
现在基础工作已经完成。随着应用的运行,我们可以向Amazon SNS Topic发送消息并从队列中读取这些消息。在repo中,我已经创建了一个控制器来触发事件的发布。如果你下载了示例应用程序,你应该能够运行它,进入http://localhost:8080/create-order,将触发发布者,而发布者又将触发消费者。在应用程序的日志中,你应该可以看到。这意味着我们的生产者代码将代码发布到SNS主题上。订阅该主题的两个队列接收这些消息,最后,消费者接收这些消息并打印出事件。
Received order event for consumer 1:
Event{eventId='386cca76-3669-4d36-b2db-01f388bbce5f', version='0',
occurredAt='2021-06-19T13:42:32.946781Z', data=EventData(orderId=9a002b95-f10b-428e-8d39-cae90d1e631d,
owner=SampleProducer, eventType=ORDER_CREATED)}
Received order event for consumer 2:
Event{eventId='386cca76-3669-4d36-b2db-01f388bbce5f', version='0', occurredAt='2021-06-19T13:42:32.946781Z',
data=EventData(orderId=9a002b95-f10b-428e-8d39-cae90d1e631d, owner=SampleProducer, eventType=ORDER_CREATED)}
总结
LocalStack提供了一种简单方便的方式来模拟AWS服务,而无需拥有实际的AWS账户。你可以使用各种AWS服务构建应用程序,如Amazon SNS、SQS、S3 DynamoDB等。
在这篇文章中,我们已经学会了如何利用LocalStack构建一个Spring Cloud Messaging应用。我们看到Spring Cloud Messaging如何提供了一个抽象层,只需几行代码就能创建一个亚马逊SQS监听器和SNS发布器。此外,我们看到了如何使用亚马逊SNS和SQS来创建一个事件驱动的应用程序。