如何使用AWS和LocalStack的Spring Cloud Messaging

738 阅读9分钟

使用AWS和LocalStack的Spring Cloud Messaging

简介

随着对云服务需求的不断增长,Spring为整合云供应商和相关服务提供了惊人的支持。Spring Cloud for Amazon Web Services就是这样一个项目,它使我们能够使用熟悉的Spring API轻松地与AWS服务集成。

在这篇文章中,我们将研究一个简单的应用程序,它使用亚马逊SNS和SQS作为消息生产者和消费者。除此之外,我们不会创建AWS账户或直接使用AWS服务。相反,我们将使用LocalStack,它将允许我们在本地创建AWS资源。

示例应用程序可以在这里找到。

先决条件

  1. 对AWS、AWS CLI以及Amazon SQS等相关服务的基本知识。
  2. Java 11和Spring Boot的基本知识 2.4.7.
  3. DockerDocker Compose的设置。

亚马逊SNS和SQS介绍

亚马逊SNS

亚马逊SNS是简单通知服务的首字母缩写。它为开发人员提供了高度可扩展的、具有成本效益的和灵活的能力,可以从一个应用程序发布消息,并将其发送给其他应用程序。它遵循pub-sub架构,将消息从发布者传递给订阅者。它是一个高度解耦的服务,可以链接到各种来源。它可以用来发布电子邮件、向SQS发送消息、短信等。

发布者(如CloudWatch警报、S3事件、SNS、微服务)在一个主题上发布消息,然后发布到该特定主题的所有订阅者(网络服务器、电子邮件地址、Amazon SQS队列、AWS Lambda)。

SNS Architecture

亚马逊SQS

亚马逊SQS是Simple Queue Service的缩写,这个名字传达了很多信息,因为它是AWS的一个完全管理的服务,所以使用起来真的很简单。它遵循类似的消息传递语义,即生产者将消息放在队列中,消费者从中读取。

一旦被消费,该消息就必须从队列中删除。删除是很重要的,因为SQS假设处理可能失败。为了防止这种情况,在消费者收到消息后,它将从队列中隐藏一段时间,之后,如果它没有被删除,消息将再次出现在队列中。

SNS和SQS都是完全管理的轻量级、易于使用的API。您可以使用亚马逊SQS和SNS来解耦和扩展微服务、分布式系统和无服务器应用程序,并提高可靠性。

LocalStack简介

LocalStack是真正的AWS服务的一个开源模拟。它在我们的本地机器上提供了一个测试环境,其API与真正的AWS服务相同。我们只在集成环境和其他环境中切换到使用真正的AWS服务。使用LocalStack有很多理由,其中有几个理由比其他理由更重要,比如说:

  1. 能够在不与AWS互动的情况下使用AWS服务。所有的开发人员都喜欢弄脏自己的手,还有什么比尝试更好的方式来学习呢。Localstack允许你玩S3、SQS、DynamoDB、SNS等服务,这样的例子不胜枚举。
  2. 在本地开发环境中测试应用程序的能力。你可以使用LocalStack为你的应用程序编写集成测试,从而降低你的成本,增加对你的代码的信心。

与LocalStack相连的Spring云信息传递应用

Spring Cloud AWS提供了Amazon SQSAmazon 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应用程序:

  1. 克隆回购中的示例代码。转到根目录。
  2. 使用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来创建一个事件驱动的应用程序。