Skip to content

Latest commit

ย 

History

History
570 lines (406 loc) ยท 18.8 KB

spring_boot_sns_sqs.md

File metadata and controls

570 lines (406 loc) ยท 18.8 KB

๋ชฉ์ฐจ



Spring Boot ํ™˜๊ฒฝ AWS SNS์™€ SQS๋ฅผ ์ด์šฉํ•œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

SQS๋Š” AWS์—์„œ ์ œ๊ณตํ•˜๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค, ๋ถ„์‚ฐ ์‹œ์Šคํ…œ ๋ฐ ์„œ๋ฒ„๋ฆฌ์Šค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋“ฑ์„ ๋ถ„๋ฆฌํ•˜๊ณ  ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” ์™„์ „ ๊ด€๋ฆฌํ˜• ๋ฉ”์‹œ์ง€ ํ(๋Œ€๊ธฐ์—ด) ์„œ๋น„์Šค์ด๋‹ค.

SNS๋Š” ํŠน์ • ์ฃผ์ œ์— ๋Œ€ํ•œ ์•Œ๋ฆผ์„ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ๋Š” pub-sub ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์ด๋‹ค.

๋งŽ์€ ์„œ๋น„์Šค์—์„œ SNS์™€ SQS๋ฅผ ๊ฐ™์ด ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๊ฐ๊ฐ์˜ ํŠน์ง•์„ ํ™œ์šฉํ•ด ํšจ์œจ์ ์ด๊ณ  ์•ˆ์ •์ ์ธ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•œ๋‹ค.

AWS์—์„œ๋„ ์ถ”์ฒœํ•˜๋Š” ์กฐํ•ฉ์ด๋ฏ€๋กœ ๋‚ด๊ตฌ์„ฑ๊ณผ ํ™•์žฅ์„ฑ๋ฉด์—์„œ๋Š” ์ถฉ๋ถ„ํžˆ ๋ฏฟ๊ณ  ์‚ฌ์šฉํ• ๋งŒํ•˜๋‹ค๊ณ  ํŒ๋‹จ๋œ๋‹ค.


SNS์™€ SQS ๊ตฌ์กฐ.
SNS๋Š” SQS๋ง๊ณ ๋„ ๋‹ค์–‘ํ•œ Subscription์„ ์ง€์›ํ•œ๋‹ค.

์ด๋ฒˆ ๊ธ€์€ Spring Boot ํ™˜๊ฒฝ์—์„œ AWS SNS์™€ SQS๋ฅผ ์ด์šฉํ•œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๊ณผ์ •์„ ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณธ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด Spring ํ™˜๊ฒฝ์—์„  SNS์™€ SQS๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์‚ดํŽด๋ณธ๋‹ค.


์ด๋ฒˆ ๊ธ€์€ AWS SNS์™€ SQS์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋‹ค๋ฃจ์ง€์•Š์œผ๋ฉฐ, Spring ๊ด€์ ์—์„œ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€์— ๋Œ€ํ•ด์„œ๋งŒ ๋‹ค๋ฃฌ๋‹ค.

์ด์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ AWS SNS๊ฐœ๋…๊ณผ SQS ์—ฐ๋™, AWS SQS์„ ์ฐธ๊ณ .

๋˜ํ•œ, EC2์— Boot๋ฅผ ๋„์›Œ์„œ ์‹ค์Šตํ•œ๋‹ค. ์™ธ๋ถ€ ์„œ๋ฒ„์—์„œ์˜ ์ด๋ฒคํŠธ ๋ฐœํ–‰ ๋ฐ ์†Œ๋น„๋Š” ๋‹ค๋ฅธ ๊ธ€์„ ์ฐธ๊ณ ํ•˜๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ด๋ฒˆ ๊ธ€์—์„œ ์‹ค์Šตํ•œ ๋ชจ๋“  ์ฝ”๋“œ๋Š” Github์„ ์ฐธ๊ณ .


1 ์˜ˆ์‹œ ์ฝ”๋“œ ์ž‘์„ฑ ๋ฐ ์˜์กด์„ฑ ์„ค์ •

๋จผ์ € ์‹ค์Šต์„ ์œ„ํ•ด ๊ฐ„๋‹จํ•œ ์ด๋ฒคํŠธ ์˜ˆ์‹œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  SNS, SQS๋ฅผ ํ™œ์šฉํ•˜๊ธฐ์œ„ํ•œ ์˜์กด์„ฑ์„ ์„ค์ •ํ•œ๋‹ค.


1-1 ์ด๋ฒคํŠธ ์˜ˆ์‹œ ์ฝ”๋“œ ์ž‘์„ฑ

์ด๋ฒˆ ๊ธ€์—์„œ ์‚ฌ์šฉํ•  ์ด๋ฒคํŠธ ์˜ˆ์‹œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

EventMessageSample.java

@Getter
public class EventMessageSample {

    private Long id;
    private String message;

    private EventMessageSample() {
    }

    public EventMessageSample(Long id, String message) {
        this.id = id;
        this.message = message;
    }
}

์ด๋ฒคํŠธ ๋‚ด์šฉ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋งˆ๋‹ค ๊ต‰์žฅํžˆ ์ƒ์ดํ•˜๋ฏ€๋กœ, ์ด๋ฒˆ ๊ธ€์—์„  ๊ฐ„๋‹จํžˆ id์™€ message๋งŒ์„ ๋‹ด๋Š” ์ด๋ฒคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.


1-2 ์˜์กด์„ฑ ์„ค์ •

Spring Cloud AWS ๋ฉ”์‹œ์ง• ๋ชจ๋“ˆ์€ ๋…๋ฆฝ ์‹คํ–‰ํ˜• ๋ชจ๋“ˆ๋กœ ์ œ๊ณต๋˜๋ฉฐ ์•„๋ž˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋งŒ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

dependencyManagement {
    imports {
        mavenBom("io.awspring.cloud:spring-cloud-aws-dependencies:2.4.4")
    }
}

dependencies {
    ...
    implementation 'io.awspring.cloud:spring-cloud-starter-aws-messaging'
    ...
}

Spring Cloud AWS๋Š” SQS ๋˜๋Š” SNS๋ฅผ ํ†ตํ•œ ๋ฉ”์‹œ์ง€ pub, sub์„ ๊ฐ„์†Œํ™”ํ•œ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

SQS๋Š” Spring 4.0์— ๋„์ž…๋œ ๋ฉ”์‹œ์ง• API (org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler)์— ์ „์ ์œผ๋กœ ์˜์กดํ•˜๋ฏ€๋กœ ์‰ฝ๊ฒŒ ์• ๋…ธํ…Œ์ด์…˜๋งŒ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•˜๋‹ค.

๋ฐ˜๋ฉด SQS๋Š” ๊ฐ๊ฐ์˜ ์•Œ๋ฆผ๋งˆ๋‹ค ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผํ•  ๋ถ€๋ถ„์ด์žˆ์–ด ๋ถ€๋ถ„์ ์œผ๋กœ ์กฐ๊ธˆ ๊ตฌํ˜„ํ•ด์ค˜์•ผํ•œ๋‹ค.


๐Ÿ’โ€โ™‚๏ธ ์˜์กด์„ฑ ๊ด€๋ จ ์•Œ์•„๋‘๋ฉด ์ข‹์€ ์†Œ์‹

์•„๋งˆ Spring ํ™˜๊ฒฝ์—์„œ์˜ SNS, SQS ์˜์กด์„ฑ๊ด€๋ จ ๋‹ค์–‘ํ•œ ์ž๋ฃŒ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์•„๋ž˜ ๋‘ ๊ฐ€์ง€๊ฐ€ ๋‚˜์˜จ๋‹ค.

  • org.springframework.cloud
  • io.awspring.cloud

๋ฌด์—‡์„ ์‚ฌ์šฉํ•ด์•ผํ• ์ง€ ํ—ท๊ฐˆ๋ฆฐ๋‹ค.

๊ด€๋ จํ•ด์„œ ๊ตฌ๊ธ€๋งํ•ด๋ณด๋ฉด org.springframework.cloud๋Š” 2.2.x๊นŒ์ง€๋งŒ ์ง€์›ํ•˜๋ฉฐ, ๊ทธ ์ด์ƒ๋ถ€ํ„ฐ๋Š” io.awspring.cloud๋กœ ์ตœ์‹ ํ™”๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๊ด€๋ จ๋œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ์—์„œ ํ™•์ธ๊ฐ€๋Šฅํ•˜๋‹ค.

์Šคํ”„๋ง ๋ฒ„์ „์— ๋”ฐ๋ฅธ ์˜์กด์„ฑ ๋ฒ„์ „๊ด€๋ จํ•ด์„œ๋Š” Github - io.awspring.cloud์—์„œ ์ž์„ธํžˆ ์•Œ ์ˆ˜ ์žˆ๋‹ค.


์ด๋ฒˆ ๊ธ€์—์„  ์•„๋ž˜ ๋ฒ„์ „์œผ๋กœ ์ง„ํ–‰๋œ๋‹ค.

  • Spring Boot 2.7.14
  • spring-cloud-starter-aws-messaging 2.4.4

2 SNS, SQS ๊ด€๋ จ ์„ค์ •

๋ณธ๊ฒฉ์ ์œผ๋กœ Spring Boot ํ”„๋กœ์ ํŠธ์—์„œ SNS์— ๋ฉ”์‹œ์ง€๋ฅผ Publishํ•˜๊ณ  SQS๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๋ฅผ Consumeํ•˜๊ธฐ์ „์— SNS์™€ SQS์— ๋Œ€ํ•œ ์„ค์ •์„ ๋จผ์ € ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.


2-1 credentials ์„ค์ •

SNS์™€ SQS ์„œ๋น„์Šค๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋ฆฌ์ „ ์„ค์ •๊ณผ ์ธ์ฆ์ •๋ณด ์„ค์ •์„ ํ•ด์ค€๋‹ค.

AwsConfiguation.java

@Getter
@Configuration
public class AwsConfiguration {
    @Value("${cloud.aws.credentials.access-key}")
    private String awsAccessKey;

    @Value("${cloud.aws.credentials.secret-key}")
    private String awsSecretKey;

    /**
     * SNS ์„ค์ •
     */
    @Bean
    public AmazonSNS amazonSNS() {
        return AmazonSNSClient.builder()
//                .withCredentials(getAwsCredentialsProvider())
                .withRegion(Regions.AP_NORTHEAST_2)
                .build();
    }

    /**
     * AWS Credential ์„ค์ •
     */
    public AWSCredentials getAwsCredentials() {
        return new BasicAWSCredentials(awsAccessKey, awsSecretKey);
    }

    public AWSCredentialsProvider getAwsCredentialsProvider() {
        return new AWSStaticCredentialsProvider(getAwsCredentials());
    }

    /**
     * SQS ์„ค์ •
     */
    @Bean
    public AmazonSQSAsync amazonSqs() {
        return AmazonSQSAsyncClientBuilder
                .standard()
//                .withCredentials(new AWSStaticCredentialsProvider(getAwsCredentials()))
                .withRegion(Regions.AP_NORTHEAST_2)
                .build();
    }
}

ํ•„์ž์˜ ๊ฒฝ์šฐ AWS๋‚ด์˜ ๊ฐ™์€ ๋ฆฌ์ „ ๊ฐ™์€ VPC์•ˆ์—์„œ ๋™์ž‘ํ•˜๋ฏ€๋กœ, ์ธ์ฆ์ •๋ณด๋„ ๋ชจ๋‘ ๋ฏธ๋ฆฌ IAM ์„ค์ •์„ํ†ตํ•ด ํ•ด์คฌ์œผ๋ฏ€๋กœ ์œ„ ์„ค์ •์—์„  ์ฃผ์„์ฒ˜๋ฆฌํ•ด๋‘์—ˆ๋‹ค.

๋งŒ์•ฝ AWS๋‚ด์—์„œ EC2, SNS, SQS๋“ฑ์— IAM ์„ค์ •์„ ํ•˜์ง€์•Š๊ณ , ์‚ฌ์šฉํ•œ๋‹ค๋ฉด Credentials ์„ค์ •์„ ํ•ด์ค˜์•ผ SNS์— ์ด๋ฒคํŠธ๋ฅผ Publish ํ•  ์ˆ˜ ์žˆ๋‹ค.

์™ธ๋ถ€ ์„œ๋ฒ„์—์„œ ์ ‘์†ํ•œ๋‹ค๊ณ  ํ• ๋•Œ๋„ ๋‹น์—ฐํžˆ Credentials ์„ค์ •์„ ํ•ด์ค˜์•ผํ•œ๋‹ค.


2-2 SQS ๊ด€๋ จ Long Polling ์„ค์ •


๐Ÿ’โ€โ™‚๏ธ Short Polling์™€ Long Polling

AWS๋Š” spring-cloud-starter-aws-messaging ๋ชจ๋“ˆ์— SQS๋ฅผ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž˜ ํ†ตํ•ฉํ•ด๋†“์•˜๋‹ค.

๊ฐœ๋ฐœ์ž๋Š” ๊ทธ์ € @SqsListiner๋ฅผ ์ด์šฉํ•˜๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ, ์•„๋ฌด ์„ค์ •์—†์ด ์‚ฌ์šฉํ•˜๋ฉด ๋””ํดํŠธ๋กœ Short Polling์„ ํ•˜๊ฒŒ๋œ๋‹ค.

Short Polling์€ Consumer์—์„œ ๋ฉ”์‹œ์ง€ Polling์‹œ SQS ์„œ๋ฒ„์˜ ํ•˜์œ„ ์„ธํŠธ๋ฅผ ์ƒ˜ํ”Œ๋งํ•˜๊ณ  ํ•ด๋‹น ์„œ๋ฒ„์—์„œ๋งŒ ๋ฉ”์‹œ์ง€๋ฅผ ํƒ์ƒ‰ํ•˜์—ฌ Pollingํ•ด์˜จ๋‹ค.

๋”ฐ๋ผ์„œ ๋ฉ”์‹œ์ง€๊ฐ€ ์กด์žฌํ•จ์—๋„ ReceiveMessage ์š”์ฒญ์ด ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ํƒ์ƒ‰ํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฐ˜๋ฉด, Long Polling์‹œ ์ผ์ • ์‹œ๊ฐ„๋™์•ˆ ์ƒ˜ํ”Œ๋ง์ด ์•„๋‹Œ ๋ชจ๋“  SQS ์„œ๋ฒ„๋ฅผ ์กฐํšŒํ•˜์—ฌ ๋ฉ”์‹œ์ง€๋ฅผ ํƒ์ƒ‰ ๋ฐ ๋ฐ˜ํ™˜ํ•œ๋‹ค.


๋ฌด์Šจ ์ฐจ์ด๊ฐ€ ์žˆ๊ฒ ๋‚˜ํ•˜๊ฒ ์ง€๋งŒ, AWS ๊ณต์‹๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋ช…์‹œ๋˜์–ด์žˆ๋‹ค.

Long polling helps reduce the cost of using Amazon SQS by eliminating the number of empty responses (when there are no messages available for a ReceiveMessage request) and false empty responses (when messages are available but aren't included in a response)

์ฐธ๊ณ : https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html#sqs-long-polling

ํ•ด์„ํ•˜๋ฉด Short Polling์‹œ ๋นˆ ์‘๋‹ต ์ˆ˜๊ฐ€ ๋งŽ์•„ SQS ์‚ฌ์šฉ ๋น„์šฉ์ด ๋” ๋งŽ์ด ๋ถ€๊ณผ๋œ๋‹ค๊ณ ํ•œ๋‹ค.

๋ฐ˜๋ฉด, Long Polling ์‚ฌ์šฉ์‹œ ๋นˆ ์‘๋‹ต์˜ ์ˆ˜๋ฅผ ์ค„์—ฌ SQS ๋น„์šฉ์„ ์ ˆ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ด€๋ จํ•œ ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋ฅผ ์ฐธ๊ณ .


๐Ÿ’โ€โ™‚๏ธ Long Polling ์„ค์ •

AwsConfiguration.java

@Getter
@Configuration
public class AwsConfiguration {
    
    // ... Credentials, Region ์„ค์ • ...

    /**
     * SQS๋Š” @SqsListener ์ด์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ Consume ํ•  ์ˆ˜ ์žˆ๋‹ค.
     *
     * ๋‹ค๋งŒ, ๋””ํดํŠธ๋กœ๋Š” Short Pollingํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ Long Polling ์ด์šฉํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•ด์ค˜์•ผํ•œ๋‹ค.
     *
     * ๊ทธ์™ธ์—๋„ Visibility ์„ค์ •๋“ฑ SQS์˜ ๋ฉ”์‹œ์ง€๋ฅผ Consume ํ•  ๋•Œ์˜ ๋‹ค์–‘ํ•œ ์„ค์ •์„ ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
     */
    @Bean
    public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSqs,
                                                                                       SimpleAsyncTaskExecutor simpleAsyncTaskExecutor) {
        SimpleMessageListenerContainerFactory factory = new SimpleMessageListenerContainerFactory();
        factory.setAmazonSqs(amazonSqs);
        factory.setWaitTimeOut(10); // polling ์„ค์ •
        factory.setVisibilityTimeout(30);
        factory.setTaskExecutor(simpleAsyncTaskExecutor);
        return factory;
    }

    @Bean
    public SimpleAsyncTaskExecutor simpleAsyncTaskExecutor() {
        SimpleAsyncTaskExecutor simpleAsyncTaskExecutor = new SimpleAsyncTaskExecutor();
        simpleAsyncTaskExecutor.setConcurrencyLimit(50);
        return simpleAsyncTaskExecutor;
    }
}

Consumer๊ฐ€ Pollingํ•  ๋•Œ์˜ ๋ชจ๋“  ์„ค์ •์„ ์œ„์™€ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ๋‹ค.

์œ„์™€ ๊ฐ™์ด Long Polling ์™ธ์—๋„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์Šค๋ ˆ๋“œ๋‚˜ VisibilityTimeout๋“ฑ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.


2-3 SNS, SQS properties

๋งˆ์ง€๋ง‰์œผ๋กœ SNS์™€ SQS๋ฅผ ํŠน์ •ํ•  ์ˆ˜ ์žˆ๋Š” ์„ค์ •์„ properties์— ํ•ด์ค€๋‹ค.

application.yml

cloud:
  aws:
    credentials:
      access-key: accesskey
      secret-key: secretkey
    stack:
      auto: false

sns-topic:
  binghe-test-sns: "arn:aws:sns:ap-northeast-2:385423560848:binghe-test-sns"

sqs-event:
  binghe-test-sqs: "binghe-test"

3 SNS ์ด๋ฒคํŠธ ๋ฐœํ–‰

์„ค์ •์ด ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด, ๊ฐ„๋‹จํžˆ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ SNS์— Publish ํ•ด๋ณธ๋‹ค.


๐Ÿ’โ€โ™‚๏ธ Publisher ๊ตฌํ˜„

AwsSnsPublisher.java

@Slf4j
@Component
@RequiredArgsConstructor
public class AwsSnsPublisher {

    private final AmazonSNS amazonSNS;
    private final ObjectMapper objectMapper;

    public void publishJson(String topic, Object message) {
        try {
            publishToSns(topic, objectMapper.writeValueAsString(message));
        } catch (JsonProcessingException e) {
            log.error("[TOPIC::{}] Json serializing fail {}", topic, message, e.getMessage(), e);
        }
    }

    private PublishResult publishToSns(String topic, String message) {
        PublishRequest publishRequest = new PublishRequest()
                .withTopicArn(topic)
                .withMessage(message)
                .addMessageAttributesEntry("contentType",
                        new MessageAttributeValue()
                                .withDataType("String")
                                .withStringValue(APPLICATION_JSON_UTF8_VALUE));

        PublishResult result = amazonSNS.publish(publishRequest);
        log.info("[TOPIC::{}] published MessageID : {}, message : {}", topic, result.getMessageId(), message);
        return result;
    }
}

๊ตฌํ˜„ ๋ฐฉ์‹์€ ๊ฐ ์ƒํ™ฉ์— ๋งž์ถฐ ๊ตฌํ˜„ํ•ด์ฃผ๋ฉด๋œ๋‹ค.


๐Ÿ’โ€โ™‚๏ธ ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰ ๊ฐ„๋‹จ ์˜ˆ์‹œ ๊ตฌํ˜„

๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ„๋‹จํžˆ ๋ฐœํ–‰ํ•ด๋ณด๊ธฐ์œ„ํ•ด ๊ฐ„๋‹จํžˆ ํ…Œ์ŠคํŠธ API๋ฅผ ๋งŒ๋“ค์–ด Publish ํ•ด๋ณธ๋‹ค.

@RestController
public class TestController {

    private final AwsSnsPublisher publisher;
    private final String topic;

    public TestController(AwsSnsPublisher publisher,
                          @Value("${sns-topic.binghe-test-sns}") String topic) {
        this.publisher = publisher;
        this.topic = topic;
    }

    @PostMapping("/test")
    public ResponseEntity<String> test(@RequestBody EventMessageSample eventMessageSample) {
        publisher.publishJson(topic, eventMessageSample);
        return ResponseEntity.ok("ok");
    }
}

API๋ฅผ ํ˜ธ์ถœํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด SQS์— ๋ฉ”์‹œ์ง€๊ฐ€ ์Œ“์ด๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


4 SQS ์ด๋ฒคํŠธ ์ˆ˜์‹ 

์ด์ œ SQS ์ด๋ฒคํŠธ๋ฅผ Consumeํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณธ๋‹ค.


๐Ÿ’โ€โ™‚๏ธ ๋ฆฌ์Šค๋„ˆ ๊ตฌํ˜„

Consumeํ•˜๋Š” ๋กœ์ง์€ @SqsListiner๋ฅผ ์ด์šฉํ•˜๋ฉด ์†์ˆ˜ @Scheduler๋กœ Pollingํ•˜์ง€ ์•Š๊ณ  ํŽธ๋ฆฌํ•˜๊ฒŒ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ธฐํƒ€ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ์ •์ฑ…๊ณผ ๊ฐ™์€ ์˜ต์…˜๋„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Slf4j
@Component
public class AwsSqsConsumer {

    @SqsListener(value = "${sqs-event.binghe-test-sqs}", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
    public void consume(@Payload EventMessageSample event, @Headers Map<String, String> headers) {
        log.info("[Consumed Message] id : {}, message : {}", event.getId(), event.getMessage());

//        SqsMessageDeletionPolicy.NEVER ์„ค์ •์‹œ ๋ช…์‹œ์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•˜๋„๋ก ack ์‘๋‹ต์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
//        ack.acknowledge();
    }
}

์‹คํ–‰ํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์œ„์—์„œ SNS๋กœ Publishํ•œ ์ด๋ฒคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ Consumeํ•  ์ˆ˜ ์žˆ๋‹ค.


๐Ÿ’โ€โ™‚๏ธ ์‚ญ์ œ ์ •์ฑ…

๋ฉ”์„ธ์ง€๋ฅผ Pollingํ•˜๊ณ  ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•œํ›„์— ๋ฉ”์‹œ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ์–ธ์ œ ์‚ญ์ œํ• ์ง€๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•„์ž๋Š” ON_SUCCESS๋งŒ ์‚ฌ์šฉํ•ด๋ณด๊ธดํ–ˆ์œผ๋‚˜, ๋กœ์ง์— ๋”ฐ๋ผ ๋‹ค์–‘ํ•˜๊ฒŒ ์„ค์ •ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๋“ฏ ํ•˜๋‹ค.


5 ์ž์ฃผ ์‹ค์ˆ˜ํ•˜๋Š” ๋ถ€๋ถ„

๊ตฌํ˜„ํ•˜๋ฉด์„œ ์ž์ฃผ ์‹ค์ˆ˜ํ•˜๋Š” ๋ถ€๋ถ„์„ ์ •๋ฆฌํ•œ๋‹ค.


5-1 IAM, Access Policy ์„ค์ •

Spring Boot์—์„œ SNS๋กœ ์ด๋ฒคํŠธ๋ฅผ Publishํ•  ๋•Œ ์ธ์ฆ ๊ด€๋ จ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, SNS ์•ก์„ธ์Šค ์ฒ˜๋ฆฌ ๋ฐฉ์‹์„ ์‚ดํŽด๋ด์•ผํ•œ๋‹ค.

SNS์—์„  ์•ก์„ธ์Šค ์ฆ๋ช…ํ•  ๋•Œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ์‹์„ ์ œ๊ณตํ•œ๋‹ค.

์ฐธ๊ณ : https://docs.aws.amazon.com/ko_kr/sns/latest/dg/sns-using-identity-based-policies.html


ํ•„์ž๋Š” ๋ชจ๋‘ AWS ํ™˜๊ฒฝ์—์„œ IAM ๊ธฐ๋ฐ˜์œผ๋กœ ์•ก์„ธ์Šค ์ฒ˜๋ฆฌ๋ฅผ ํ–ˆ์œผ๋ฏ€๋กœ, ์ด์™€ ๊ด€๋ จ๋œ ๋‚ด์šฉ๋งŒ ๊ฐ„๋‹จํžˆ ์ •๋ฆฌํ•ด๋ณด๋ฉด..

  • EC2์—์„œ SNS์— ์ด๋ฒคํŠธ๋ฅผ ์ „์†กํ•˜๋ ค๋ฉด IAM ์„ค์ •.
  • SNS์— ๋ฉ”์‹œ์ง€๋ฅผ Publishํ•˜๋Š” EC2๋Š” IAM์— SNS ๊ด€๋ จ ๋ฉ”์‹œ์ง€ Publish ๊ถŒํ•œ์ด ์žˆ์–ด์•ผํ•˜๋ฉฐ,
  • SQS๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๋ฅผ Consumeํ•˜๋Š” EC2๋Š” IAM์— ๋ฉ”์‹œ์ง€ Consume ๊ถŒํ•œ์ด ์žˆ์–ด์•ผํ•œ๋‹ค.

๐Ÿ’โ€โ™‚๏ธ Publisher, Consumer IAM ์„ค์ •

SNS์™€ SQS์— ์ ‘๊ทผํ•˜๋Š” Publisher์™€ Consumer ์„œ๋ฒ„์— ๋ชจ๋‘ IAM๋ฅผ ์„ค์ •ํ•ด์ค€๋‹ค.

ํ•„์ž๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ™์€ ๊ณ„์ •๋‚ด์˜ SNS์™€ SQS์˜ ํ’€ ์•ก์„ธ์Šค ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ IAM Role์„ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

IAM๊ณผ ๊ด€๋ จ๋œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ IAM ๊ฐœ๋…๋ฅผ ์ฐธ๊ณ .


๐Ÿ’โ€โ™‚๏ธ SNS Access Policy ์„ค์ •

{
  "Version": "2008-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__default_statement_ID",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "SNS:GetTopicAttributes",
        "SNS:SetTopicAttributes",
        "SNS:AddPermission",
        "SNS:RemovePermission",
        "SNS:DeleteTopic",
        "SNS:Subscribe",
        "SNS:ListSubscriptionsByTopic",
        "SNS:Publish",
        "SNS:Receive"
      ],
      "Resource": "arn:aws:sns:ap-northeast-2:{source owner id}:binghe-test-sns",
      "Condition": {
        "StringEquals": {
          "AWS:SourceOwner": "{source owner id}"
        }
      }
    }
  ]
}

๊ทธ๋ฆฌ๊ณ  SNS ์ƒ์„ฑํ•  ๋•Œ๋‚œ ์ƒ์„ฑํ•˜๊ณ ๋‚˜์„œ Acces Policy๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์œ„์™€ ๊ฐ™์ด ๊ฐ™์€ ๊ณ„์ •๋‚ด์—์„œ์˜ ์•ก์„ธ์Šค ๊ถŒํ•œ์„ ๋ชจ๋‘ ๋“ฑ๋กํ•ด์ค€๋‹ค.


๐Ÿ’โ€โ™‚๏ธ SQS Access Policy ์„ค์ •

๋งˆ์ง€๋ง‰์œผ๋กœ SNS -> SQS๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ฒŒ, SQS์— SNS๋กœ๋ถ€ํ„ฐ์˜ Access Policy๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

{
  "Version": "2012-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__owner_statement",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::{source owner id}:root"
      },
      "Action": "SQS:*",
      "Resource": "arn:aws:sqs:ap-northeast-2:{source owner id}:binghe-test"
    },
    {
      "Sid": "topic-subscription-arn:aws:sns:ap-northeast-2:{source owner id}:binghe-test-sns",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "SQS:SendMessage",
      "Resource": "arn:aws:sqs:ap-northeast-2:{source owner id}:binghe-test",
      "Condition": {
        "ArnLike": {
          "aws:SourceArn": "arn:aws:sns:ap-northeast-2:{source owner id}:binghe-test-sns"
        }
      }
    }
  ]
}

5-2 Enable Raw Message Delivery

SNS๋Š” SQS๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์•„๋ž˜์™€ ๊ฐ™์ด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋‚ด์šฉ์„ Message Body์— ์ถ”๊ฐ€ํ•˜์—ฌ ์ „๋‹ฌํ•œ๋‹ค.

Raw Message ์„ค์ •์„ ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ

{
  "Type" : "Notification",
  "MessageId" : "113432bb-e413-5c3b-8281-6f876adba7e4",
  "TopicArn" : "arn:aws:sns:ap-northeast-2:{source owner id}:binghe-test-sns",
  "Message" : "{\"id\":\"qwerqwer\",\"message\":\"test message 5\"}", // ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ
  "Timestamp" : "2023-07-27T15:26:00.012Z",
  "SignatureVersion" : "1",
  "Signature" : "xxxx",
  "SigningCertURL" : "https://sns.ap-northeast-2.amazonaws.com/SimpleNotificationService-xxxxxxxxxxxxx.pem",
  "UnsubscribeURL" : "https://sns.ap-northeast-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-2:{source owner id}:binghe-test-sns:xxxxxxxxxx",
  "MessageAttributes" : {
    "contentType" : {"Type":"String","Value":"application/json;charset=UTF-8"}
  }
}

์ด๋ ‡๊ฒŒ๋˜๋ฉด Consumer์—์„œ Consumeํ•  ๋•Œ Jackson์ด ์ œ๋Œ€๋กœ ์—ญ์ง๋ ฌํ™”ํ•˜์ง€๋ชปํ•ด ๋ชจ๋“  ๊ฐ’์— null์ด ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ SNS -> SQS ์—ฐ๋™ํ•˜๋Š” ์„ค์ •์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด Enable Raw Message Delivery ์„ค์ •์„ ์ฒดํฌํ•ด์ค˜์•ผ ์ œ๋Œ€๋กœ ์—ญ์ง๋ ฌํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.


์ฐธ๊ณ