RabbitMQ 활용하기

서론

최근 RabbitMQ를 활용하여 이메일, SMS, 카카오톡 푸시 알림 등을 처리하는 Notification Application에 대해 개발하게 되었습니다. 때문에 본 포스팅에서는 RabbitMQ를 활용하여 Nofification Apllication에서 주로 사용하는 기능들에 대해 알아보는 시간을 갖도록 하겠습니다. 특히 Spring Boot와의 연동을 통해 실무에서 많이 사용되는 큐 전략과 예제 코드를 상세히 다루어, 처음 접하시는 분들도 쉽게 이해할 수 있도록 하겠습니다.


1. RabbitMQ란?

RabbitMQ는 오픈 소스 메시지 브로커 소프트웨어로, 다양한 프로토콜을 지원하며 메시지의 송신자와 수신자 사이에서 큐잉을 담당합니다. 시스템 간의 비동기 통신을 가능하게 하여, 애플리케이션의 확장성과 유연성을 높여줍니다.

 

2. 왜 Notification Application에 RabbitMQ를 사용하나?

Notification Application은 대량의 메시지를 외부로 발송해야 하며, 이러한 작업은 시간이 많이 소요될 수 있습니다. RabbitMQ를 사용하면 다음과 같은 이점이 있습니다:

  • 비동기 처리: 메시지 발송을 비동기로 처리하여 애플리케이션의 응답 속도를 향상시킵니다.
  • 부하 분산: 큐를 통해 작업을 분산하여 서버 부하를 줄입니다.
  • 신뢰성: 메시지의 안전한 전달과 재처리를 지원합니다.

 

3. RabbitMQ 설정 및 SpringBoot 연동하기

3.1 RabbitMQ 설치하기

RabbitMQ를 사용하려면 로컬 또는 서버에 RabbitMQ 서버를 설치해야 합니다. 보통 도커를 활용해서 설치 및 운영합니다.

docker run -d --hostname my-rabbit --name some-rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3-management

 

3.2 프로젝트 설정 (Spring Initializr)

start.spring.io 사이트에서 최신 버전의 스프링 부트를 설치합니다 (포스팅 기준 3.3.3). Dependencies는 Spring Web, Spring for RabbitMQ, Lombok 등을 주입해줍니다. 저는 Maven 빌드툴 보다는 Gradle을 선호합니다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-amqp'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

 

3.3 설정파일 구성하기

application.yml vkdlfdp RabbitMQ 연결 설정을 추가해봅니다.

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

 

 

4. 큐 전략 및 관리하기

4.1 Exchange, Qeue, Routing Key 이해

  • Exchange: 메시지를 받아서 적절한 큐로 라우팅하는 역할을 합니다.
  • Queue: 메시지가 저장되는 버퍼입니다.
  • Routing Key: 메시지를 특정 큐로 라우팅하기 위한 키 입니다.

 

4.2 Direct, Topic, Fanout Exchange 사용 전략

 

  • Direct Exchange: 정확한 Routing Key 매칭이 필요할 때 사용합니다.
  • Topic Exchange: 패턴 매칭을 통해 다수의 큐에 메시지를 전달할 때 사용합니다.
  • Fanout Exchange: 모든 바인딩된 큐에 메시지를 브로드캐스팅합니다.
    • 이메일은 Direct Exchange를 사용하여 특정 큐로 전달.
    • 공지사항은 Fanout Exchange를 사용하여 모든 사용자에게 전달.

 

4.3 Dead Letter Queue 설정

메시지 처리 중 에러가 발생했을 때 해당 메시지를 별도의 큐로 보내어 추후에 재처리하거나 로그를 분석할 수 있습니다.

@Bean
public Queue deadLetterQueue() {
    return QueueBuilder.durable("deadLetterQueue").build();
}

 

 

4.4 Priority Queue 활용

메시지의 우선순위를 지정하여 중요한 메시지를 먼저 처리할 수 있습니다.

@Bean
public Queue priorityQueue() {
    return QueueBuilder.durable("priorityQueue")
            .withArgument("x-max-priority", 10)
            .build();
}

 

 

 

 

5. 실전 예제

5.1 이메일 발송 구현 예제

프로듀서 (메시지 발송자)

@Service
public class EmailSender {

    private final AmqpTemplate amqpTemplate;

    @Value("${email.exchange}")
    private String exchange;

    @Value("${email.routingkey}")
    private String routingKey;

    public void sendEmail(Email email) {
        amqpTemplate.convertAndSend(exchange, routingKey, email);
    }
}

 

컨슈머 (메시지 수신자)

@Component
public class EmailReceiver {

    @RabbitListener(queues = "${email.queue}")
    public void receiveEmail(Email email) {
        // 이메일 발송 로직 구현
        System.out.println("Received Email: " + email.toString());
    }
}

 

Config Class

@Configuration
public class EmailRabbitConfig {

    @Value("${email.queue}")
    private String queueName;

    @Value("${email.exchange}")
    private String exchange;

    @Value("${email.routingkey}")
    private String routingKey;

    @Bean
    public Queue emailQueue() {
        return new Queue(queueName, false);
    }

    @Bean
    public DirectExchange emailExchange() {
        return new DirectExchange(exchange);
    }

    @Bean
    public Binding emailBinding(Queue emailQueue, DirectExchange emailExchange) {
        return BindingBuilder.bind(emailQueue).to(emailExchange).with(routingKey);
    }
}

 

application.yml

email.queue=emailQueue
email.exchange=emailExchange
email.routingkey=emailRoutingKey

 

5.2 SMS 발송 구현 예제

프로듀서

@Service
public class SmsSender {

    private final AmqpTemplate amqpTemplate;

    @Value("${sms.exchange}")
    private String exchange;

    @Value("${sms.routingkey}")
    private String routingKey;

    public void sendSms(Sms sms) {
        amqpTemplate.convertAndSend(exchange, routingKey, sms);
    }
}

 

컨슈머

@Component
public class SmsReceiver {

    @RabbitListener(queues = "${sms.queue}")
    public void receiveSms(Sms sms) {
        // SMS 발송 로직 구현
        System.out.println("Received SMS: " + sms.toString());
    }
}

 

config Class, Properties 파일은 이메일 예제와 유사하게 구성하시면 됩니다.

 

5.3 카카오톡 푸시 알림 구현 예제

프로듀서

@Service
public class KakaoPushSender {

    private final AmqpTemplate amqpTemplate;

    @Value("${kakao.exchange}")
    private String exchange;

    @Value("${kakao.routingkey}")
    private String routingKey;

    public void sendKakaoPush(KakaoPush kakaoPush) {
        amqpTemplate.convertAndSend(exchange, routingKey, kakaoPush);
    }
}

 

컨슈머

@Component
public class KakaoPushReceiver {

    @RabbitListener(queues = "${kakao.queue}")
    public void receiveKakaoPush(KakaoPush kakaoPush) {
        // 카카오톡 푸시 알림 발송 로직 구현
        System.out.println("Received Kakao Push: " + kakaoPush.toString());
    }
}

 

 

6. 실전 활용 팁 및 주의사항

6.1 멀티스레드 환경에서의 처리

컨슈머는 기본적으로 멀티스레드로 동작하므로, 스레드 안정성을 보장해야 합니다. 필요한 경우 @RabbitListener의 concurrency 속성을 조절하여 스레드 수를 관리합니다.

@RabbitListener(queues = "${email.queue}", concurrency = "5")

 

6.2 메시지 재처리 및 에러 핸들링

메시지 처리 중 예외가 발생하면 기본적으로 메시지가 재시도됩니다. 이를 방지하거나 커스텀 재시도 로직을 구현하려면 SimpleRetryPolicy와 RetryTemplate을 활용합니다.

@Bean
public RetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateless()
        .maxAttempts(3)
        .backOffOptions(1000, 2.0, 10000)
        .build();
}

 

6.3 성능 최적화

한 번에 가져오는 메시지 수를 (Prefetch Count) 조절하여 메모리 사용을 최적화 할 수 있습니다.

@RabbitListener(queues = "${email.queue}", prefetch = "10")

 

대용량 메시지의 경우 압축을 통해 전송량을 줄이기도 합니다.

 

7. 결론

이번 포스팅에서는 RabbitMQ를 활용하여 Notification Application을 개발하는 방법에 대해 알아보았습니다. 큐 전략부터 실전 예제, 그리고 활용 팁까지 다루어 보았는데, RabbitMQ를 통해 효율적이고 확장성 있는 애플리케이션을 개발하시는데 도움이 되기를 바래요 ㅎ