首頁  >  文章  >  Java  >  使用 Amazon SQS 建立 Spring Boot 消費者應用程式:使用雲端開發套件 (CDK) 設定基礎設施

使用 Amazon SQS 建立 Spring Boot 消費者應用程式:使用雲端開發套件 (CDK) 設定基礎設施

Susan Sarandon
Susan Sarandon原創
2024-10-07 22:08:30564瀏覽
第 017 天 - 100 天AWSIaCDevops 挑戰

今天,在我的 100 天程式碼挑戰系列中,我將向您展示如何使用 Amazon SQS 解耦使用 springboot 開發的微服務。

什麼是亞馬遜 SQS?

Amazon SQS(簡單佇列服務) 是一項雲端服務,可協助應用程式透過 sendyg 進行通信,在佇列中儲存和接收訊息。它就像一條等待隊列,等待消費者準備好處理它們為止。這可以防止系統失控並確保不會遺失訊息。

使用 Springboot 應用程式使用 SQS 訊息

示範如何透過建立一個處理來自 SQS 佇列的每個訊息的 Spring Boot 應用程式來使用 SQS 訊息。使用 CDK (Java) 建置的基礎設施將包括:

  • VPC,具有公用 子網路,用於託管將在其中執行 Spring Boot 應用程式的 EC2 執行個體。
  • 用於 EC2 執行個體存取網際網路並下載相依性的網際網路閘道
  • 用於訊息儲存的 SQS 佇列死信佇列
  • 用於託管 SpringBoot 應用程式的 EC2 執行個體
  • IAM 角色,允許 EC2 執行個體從 SQS 佇列檢索訊息(非常重要)。

創建基礎設施

使用 CDK (Java) 設定必要的基礎設施

Building a Spring Boot Consumer Application with Amazon SQS: Setup Infrastructure Using Cloud Development Kit (CDK)

VPC 與子網路網際網路網關


public class NetworkContruct extends Construct {
  private final IVpc vpc;
  public NetworkContruct(Construct scope, String id, StackProps props) {
    super(scope, id);
    this.vpc =
        new Vpc(
            this,
            "VpcResource",
            VpcProps.builder()
                .vpcName("my-vpc")
                .enableDnsHostnames(true)
                .enableDnsSupport(true)
                .createInternetGateway(true)
                .ipProtocol(IpProtocol.IPV4_ONLY)
                .ipAddresses(IpAddresses.cidr("10.0.0.1/16"))
                .maxAzs(1)
                .subnetConfiguration(
                    List.of(
                        SubnetConfiguration.builder()
                            .name("Public-Subnet")
                            .mapPublicIpOnLaunch(true)
                            .subnetType(SubnetType.PUBLIC)
                            .build()))
                .build());
  }
  public IVpc getVpc() {
    return vpc;
  }
}


此 cdk 構造將會建立:
??名為 my-vpc 的 VPC 並啟用 DNS 主機名稱。
??名為 Public-Subnet 的公用子網,允許資源附加公用 IP(如果配置了公用 IP)。
??用於啟用網際網路流量的網際網路閘道。

SQS隊列和死信隊列


public class QueueConstruct extends Construct {
  private final IQueue queue;
  public QueueConstruct(Construct scope, String id, IVpc vpc, StackProps props) {
    super(scope, id);
    IQueue dlq =
        new Queue(
            this,
            "DeadLetterQueue",
            QueueProps.builder()
                .deliveryDelay(Duration.millis(0))
                .retentionPeriod(Duration.days(10))
                .queueName("my-queue-dlq")
                .build());
    DeadLetterQueue deadLetterQueue = DeadLetterQueue.builder()
        .queue(dlq)
        .maxReceiveCount(32)
        .build();

    this.queue =
        new Queue(
            this,
            "SQSQueueResource",
            QueueProps.builder()
                .queueName("my-queue")
                .retentionPeriod(Duration.minutes(15))
                .visibilityTimeout(Duration.seconds(90))
                .deadLetterQueue(deadLetterQueue)
                .build());
  }

  public IQueue getQueue() {
    return queue;
  }
}


上述 CDK 構造將建立以下資源:

  • 一個名為 my-queue 的佇列,它將在 Spring Boot 應用程式中使用。
  • 一個名為 my-queue-dlq 的死信隊列,它捕獲失敗的訊息以便稍後可以分析和修復它們,而不會阻塞主隊列。

用於託管 Spring Boot 應用程式的 EC2 實例


// ComputerProps.java
public record ComputerProps(IVpc vpc, String sqsQueueArn) {}

// ComputerConstruct.java
public class ComputerConstruct extends Construct {
  private final IInstance computer;
  public ComputerConstruct(
      Construct scope, String id, ComputerProps computerProps, StackProps props) {
    super(scope, id);
    SecurityGroup securityGroup =
        new SecurityGroup(
            this,
            "WebserverSGResource",
            SecurityGroupProps.builder()
                .allowAllOutbound(true)
                .securityGroupName("Webserver-security-group")
                .disableInlineRules(true)
                .vpc(computerProps.vpc())
                .description("Allow trafic from/to webserver instance")
                .build());

    securityGroup.addIngressRule(Peer.anyIpv4(), Port.SSH, "Allow ssh traffic");
    securityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(8089), "Allow traffic from 8089 port");

    KeyPair keyPair =
        new KeyPair(
            this,
            "KeyPairResource",
            KeyPairProps.builder()
                .keyPairName("ws-keypair")
                .account(Objects.requireNonNull(props.getEnv()).getAccount())
                .type(KeyPairType.RSA)
                .format(KeyPairFormat.PEM)
                .build());

    new CfnOutput(
        this, "KeyPairId", CfnOutputProps.builder().value(keyPair.getKeyPairId()).build());

    Instance ec2Instance =
        new Instance(
            this,
            "WebServerInstanceResource",
            InstanceProps.builder()
                .securityGroup(securityGroup)
                .keyPair(keyPair)
                .instanceName("Webserver-Instance")
                .machineImage(
                    MachineImage.lookup(
                        LookupMachineImageProps.builder()
                            .name("*ubuntu*")
                            .filters(
                                Map.ofEntries(
                                    Map.entry("image-id", List.of("ami-0e86e20dae9224db8")),
                                    Map.entry("architecture", List.of("x86_64"))))
                            .windows(false)
                            .build()))
                .vpc(computerProps.vpc())
                .role(buildInstanceRole(computerProps))
                .instanceType(InstanceType.of(InstanceClass.T2, InstanceSize.MICRO))
                .associatePublicIpAddress(true)
                .blockDevices(
                    List.of(
                        BlockDevice.builder()
                            .mappingEnabled(true)
                            .deviceName("/dev/sda1")
                            .volume(
                                BlockDeviceVolume.ebs(
                                    10,
                                    EbsDeviceOptions.builder()
                                        .deleteOnTermination(true)
                                        .volumeType(EbsDeviceVolumeType.GP3)
                                        .build()))
                            .build()))
                .userDataCausesReplacement(true)
                .vpcSubnets(SubnetSelection.builder().subnetType(SubnetType.PUBLIC).build())
                .build());

    UserData userData = UserData.forLinux();
    userData.addCommands(readFile("./webserver-startup.sh"));

    ec2Instance.addUserData(userData.render());

    this.computer = ec2Instance;
  }

  public IInstance getComputer() {
    return computer;
  }

  private String readFile(String filename) {

    InputStream scriptFileStream = getClass().getClassLoader().getResourceAsStream(filename);

    try {
      assert scriptFileStream != null;
      try (InputStreamReader isr = new InputStreamReader(scriptFileStream, StandardCharsets.UTF_8);
          BufferedReader br = new BufferedReader(isr)) {
        StringBuilder content = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
          content.append(line).append("\n");
        }
        return content.toString();
      }
    } catch (IOException e) {
      throw new RuntimeException(e.getMessage());
    }
  }

  private IRole buildInstanceRole(ComputerProps props) {
    return new Role(
        this,
        "WebserverInstanceRoleResource",
        RoleProps.builder()
            .roleName("webserver-role")
            .assumedBy(new ServicePrincipal("ec2.amazonaws.com"))
            .path("/")
            .inlinePolicies(
                Map.ofEntries(
                    Map.entry(
                        "sqs",
                        new PolicyDocument(
                            PolicyDocumentProps.builder()
                                .assignSids(true)
                                .statements(
                                    List.of(
                                        new PolicyStatement(
                                            PolicyStatementProps.builder()
                                                .effect(Effect.ALLOW)
                                                .actions(
                                                    List.of(
                                                        "sqs:DeleteMessage",
                                                        "sqs:ReceiveMessage",
                                                        "sqs:SendMessage",
                                                        "sqs:GetQueueAttributes",
                                                        "sqs:GetQueueUrl"))
                                                .resources(List.of(props.sqsQueueArn()))
                                                .build())))
                                .build()))))
            .build());
  }
}


上述 CDK 構造將建立以下資源:

  • 名為Webserver-security-group 的安全群組,允許連接埠22 上的SSH 連接的入站流量,並允許連接埠8089(應用程式連接連接埠)上的入站流量。
  • 名為 ws-keypair 的金鑰對,將用於透過 SSH 連接到應用程式主機。由於我們使用 CDK 來建立基礎設施,如果部署後需要下載私鑰(PEM 檔案),請參閱我之前的文章如何在 Cloudformation 或 CDK 堆疊建立後檢索私鑰檔案 PEM[↗]。
  • 名為 Webserver-Instance 的 Ec2 實例。
  • 名為webserver-role 的Ec2 實例的IAM 角色,它允許Ec2 實例上託管的spring Boot 應用程式與Amazon SQS 佇列(已建立)建立連線並執行操作:刪除訊息、接收訊息、傳送訊息,取得隊列屬性並取得隊列Url。

創建堆疊


// MyStack.java
public class MyStack extends Stack {
  public MyStack(final Construct scope, final String id, final StackProps props) {
    super(scope, id, props);
    IVpc vpc = new NetworkContruct(this, "NetworkResource", props).getVpc();
    IQueue queue = new QueueConstruct(this, "QueueResource", vpc, props).getQueue();
    IInstance webserver =
        new ComputerConstruct(
                this, "ComputerResource", new ComputerProps(vpc, queue.getQueueArn()), props)
            .getComputer();
  }
}

// Day17App.java
public class Day017App {
  public static void main(final String[] args) {
    App app = new App();
    new MyStack(app,"Day017Stack",
        StackProps.builder()
                .env(
                    Environment.builder()
                        .account(System.getenv("CDK_DEFAULT_ACCOUNT"))
                        .region(System.getenv("CDK_DEFAULT_REGION"))
                        .build())
                .build());
    app.synth();
  }
}


創建SpringBoot消費者應用程式

為了讓事情變得簡單並避免讓我的生活變得複雜,我將使用 Spring Cloud AWS Docs[↗]

Spring Cloud AWS 簡化了在 Spring 框架和 Spring Boot 應用程式中使用 AWS 託管服務。它提供了一種使用眾所周知的 Spring 習慣用法和 API 與 AWS 提供的服務進行互動的便捷方式。

為了在我的專案中配置SQS服務,我將在配置類別中新增以下bean:


@Configuration
public class ApplicationConfiguration {
    @Bean
    public AwsRegionProvider customRegionProvider() {
        return new InstanceProfileRegionProvider();
    }
    @Bean
    public AwsCredentialsProvider customInstanceCredProvider() {
        return  InstanceProfileCredentialsProvider.builder()
                .build();
    }
}


以及捕獲所有新訊息並列印其內容的偵聽器。


@Slf4j
@Component
public class ExampleSQSConsumer {
    @SqsListener(queueNames = { "my-queue" }) // ??
    public void listen(String payload) {
        log.info("*******************  SQS Payload ***************");
        log.info("Message Content: {}", payload);
        log.info("Received At: {}", Date.from(Instant.now()));
        log.info("************************************************");
    }
}


您可以在我的 GitHub 儲存庫中找到完整的專案[↗]

部署

⚠️⚠️ 在執行部署指令之前,請確保您的主機上安裝了 java。我在 MacO 下使用 Java 21 來建置這個基礎架構。

在任意位置開啟終端機並執行以下命令:


<p>git clone https://github.com/nivekalara237/100DaysTerraformAWSDevops.git<br>
cd 100DaysTerraformAWSDevops/day_017<br>
cdk bootstrap --profile cdk-user<br>
cdk deploy --profile cdk-user Day017Stack</p>




結果

Building a Spring Boot Consumer Application with Amazon SQS: Setup Infrastructure Using Cloud Development Kit (CDK)


您可以在 GitHub Repo 上找到完整的原始碼↗

以上是使用 Amazon SQS 建立 Spring Boot 消費者應用程式:使用雲端開發套件 (CDK) 設定基礎設施的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn