SW 개발/Spring

Spring Boot, RabbitMQ 비동기 통신 구현하기

지단로보트 2021. 3. 19. 09:40

개요

  • 백엔드에서의 통신 방법은 요청에 대해 즉시 응답하는 동기식 통신이 일반적이지만, 비동기 통신 또한 백엔드 아키텍쳐에서 중요하다고 말할 수 있다. 갑작스럽게 요청이 집중되는 명절 기차표 예매, 대학교 수강신청 등이 비동기 통신이 요구되는 대표적인 사용 사례라고 볼 수 있다. (이를 동기식으로 처리하면 백엔드와 데이터베이스에 엄청난 부하가 발생한다.)
  • RabbitMQAMQP(플랫폼 독립적인 비동기 메시징을 다룬 프로토콜)을 준수하여 제작된 메시지 브로커이다. 설치가 쉽고, 기능이 직관적인데다 성능이 뛰어나기 때문에 현재 비동기 메시징에 있어서는 광범위하게 사용되고 있다. 이번 글에서는 Spring Boot에서 RabbitMQ 사용 방법을 소개하고자 한다.

AMQP 통신 원리

  • AMQP에서는 브로커의 양쪽에 Producer(주로 애플리케이션), Consumer(역시 주로 애플리케이션)가 존재한다. 그리고 이 둘 사이에 브로커의 Queue가 존재한다. Queue만 생성되어 있어도, 둘 사이에 Message(편지)를 주고 받는 것이 가능하다.
  • 만약, Producer가 전송한 Messagen개의 Queue에 전송하고 싶다면? Exchange(우체통)를 생성하면 된다. 이 경우, Producer가 전송한 Message가 바로 Queue로 가지 않는다. Exchange라 불리는 중개자가 먼저 받아 사전 정의된 Binding에 따라 일치하는 n개의 Queue로 전송한다.
  • Spring AMQP를 이용하면 앞에서 설명한 Queue, Exchange, Binding, Producer, Consumer를 모두 코드 레벨에서 생성하여 Message를 주고 받을 수 있다.

RabbitMQ 로컬 인스턴스 실행

  • 아래는 예제를 실행하기 위한 목적의 RabbitMQ 로컬 인스턴스를 실행하는 예이다. (운영 환경에서의 설치 방법은 본 블로그의 이 글을 참고한다.)
# RabbitMQ 도커 컨테이너를 실행
# 웹 콘솔 포트: 15672
# AMQP 포트: 9999
$ docker run -d --name rabbitmq -p 9999:5672 -p 15672:15672 rabbitmq:3-management-alpine
  • 도커 컨테이너를 실행 후 브라우저에서 http://localhost:15672 주소로 접속하면 RabbitMQ의 웹 콘솔이 보여진다. 초기 어드민 계정은 guest/guest이다.

build.gradle.kts

  • 프로젝트 루트의 build.gradle.kts 파일에 아래 내용을 추가한다.
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-amqp")
}

application.yml

  • /src/main/resources/application.yml 파일에 아래 내용을 추가한다. 로컬 테스트 목적의 간단한 구성으로 각자의 환경에 맞게 설정해야 한다.
spring:
  rabbitmq:
    host: localhost
    port: 9999
    username: guest
    password: guest
    template:
      reply-timeout: 30000

RabbitConfig 작성

  • RabbitMQ 통신에 필요한 Queue, Exchange, Binding을 코드 레벨에서 아래와 같이 스프링 빈으로 생성할 수 있다.
import org.springframework.amqp.core.Binding
import org.springframework.amqp.core.BindingBuilder
import org.springframework.amqp.core.DirectExchange
import org.springframework.amqp.core.Queue
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class RabbitConfig {

    // foobar.queue 큐를 생성
    @Bean
    fun foobarQueue(): Queue {

        return Queue("foobar.queue", false)
    }

    // foobar.direct.exchange 익스체인지를 생성
    @Bean
    fun foobarDirectExchange(): DirectExchange {

        return DirectExchange("foobar.direct.exchange")
    }

    // 라우팅을 맵핑한 바인딩을 생성
    @Bean
    fun foobarBinding(foobarDirectExchange: DirectExchange, foobarQueue: Queue): Binding {

        return BindingBuilder
            .bind(foobarQueue)
            .to(foobarDirectExchange)
            .withQueueName()
    }
}
  • Queue, Exchange 빈 생성시 durable 옵션은 RabbitMQ 재시작시 해당 큐를 살려둘지 여부를 의미한다. false로 설정하면 브로커가 종료되면 해당 큐가 모두 휘발된다.

Producer 작성 예

  • 아래는 스프링 빈에서 Producer로서 Message를 특정 Exchange에 전송하는 예이다.
import org.springframework.amqp.core.AmqpTemplate
import org.springframework.stereotype.Service

@Service
class FooService(
    val rabbitTemplate: AmqpTemplate
) {
    fun send(message: String) {

        // foobar.direct 익스체인지에 메시지 전송
        rabbitTemplate.convertAndSend("foobar.direct", "foobar", message)
    }
}

Consumer 작성 예

  • 아래는 스프링 빈에서 Consumer로서 Queue로부터 Message를 수신하는 예이다.
import org.springframework.amqp.rabbit.annotation.RabbitListener
import org.springframework.stereotype.Service

@Service
class FooService {

    // foobar 큐로부터 전송된 메시지를 수신
    @RabbitListener(queues = ["foobar"])
    fun listen(message: String) {

        println(message)
    }

참고 글