[서버] SpringBoot 을 이용한 RabbitMQ 구축하기

최동근·2023년 8월 15일
3

메세지 큐

목록 보기
3/4

이번 포스팅에서는 이번 포스팅에서는 메세지 큐(Message Queue) 을 알아보자 에 이어서
실제로 RabbitMQSpring boot 에 적용해보겠습니다 🔥

0. 들어가기 앞서

Spring 에 RabbitMQ 을 구축하기 앞서 정의와 필요한 개념과 구성 요소를 살펴보겠습니다 👨‍💻

RabbitMQAMQP(Advanced Message Queue Protocol) 을 구현한 오픈소스 메세지 브로커입니다.
대중적으로 많이 사용되는 메세지 브로커이며 빠르고 쉽게 구성할 수 있으며 직관적입니다.

1. [RabbitMQ 용어 정리]

  • Producer : 메세지(요청)을 보내는 주체, 메세지를 Exchange 에 Publish
  • Consumer : Producer 로 부터 메세지를 받아 처리하는 주체
  • Exchange : Producer 로 부터 전달받은 메세지를 어떤 Queue 로 보낼지 결정하는 장소
    • 하나의 Exchange 에 1개 이상의 Queue 가 등록
    • 둘 사이의 Routing Key(Binding Key) 존재
  • Queue : Consumer 가 메세지를 소비하기 전까지 메세지를 보관하는 장소
  • Binding : Exchange 와 Queue 관계를 정의 즉, Binding 에 따라서 Exchange 에 발행된 메세지가 어떤 Queue 로 갈지 결정된다.
    • 사용자가 특정 Exchange 가 특정 Queue 을 Binding 하도록 정의함
    • Binding 은 Routing Key(Binding Key) 을 수단으로 이루어짐
    • 특정 Exchange 와 Binding 된 Queue 는 해당 Exchange 에 등록되었다고 이해하면 됨

2. [Binding 전략]

앞서 살펴본바와 같이 Binding 이란 Producer 로 부터 메세지를 받은 Exchange 가 어떤 Queue 로 메세지를 전달할지에 대한 방식이였습니다.
-> ExchangeQueue 을 등록하고 Queue 에 보관되어 있는 메세지를 해당 Queue 을 바라보는 Consumer 가 소비하는 방식

여기에는 4가지 전략이 있습니다❗️

  • 🌱 Direct Exchange : 메세지의 Routing Key 와 정확히 일치하는 BindingQueue 로 Routing

Direct Exchange 에 따르면, 메세지에 부여된 Routing Key 와 동일한 Key 로 BindingQueue 에만 전송합니다.
해당 이미지는 Direct Exchange 방식을 도식화한 이미지인데, rabbit key 을 가진 메세지는 Exchange 을 거쳐 rabbit 으로 binding 된 Queue 로 전송됩니다 👨‍💻


  • 🌱 Fanout Exchange : Binding 된 모든 Queue 에 메세지를 Routing

Fanout Exchange 전략은, Routing Key 에 상관 없이 Exchange 에 등록된 모든 Queue 에 메세지를 전송합니다.


  • 🌱 Topic Exchange : 특정 Routing Patten 이 일치하는 Queue 로 Routing

Topic RoutingRouting Key 의 패턴을 이용해 메세지를 Routing 합니다.
여러 Consumer 에서 메세지 형식에 따라 선택적으로 수신해야 하는 경우 등에 사용됩니다.

위의 이미지는 Topic Routing 을 도식화한 것인데, animal.rabbit 이라는 Routing key 로 Exchange 에 메세지가 발행되었습니다.
그러면 이와 일치하는 패턴을 가지는 Routing Key 로 Binding 된 Queue 로 메세지가 전달됩니다.
animal.rabbit 에 animal 과 # 은 일치하는 패턴이기에 정상적으로 전달됩니다❗️


  • 🌱 Headers Exchange : Key-Value 로 정의된 Header 속성을 통한 Routing

Headers Exchange 는 앞서 살펴보았던 Topic Exchange 와 비슷하지만 Header 을 이용하는 차이점이 있습니다.
Producer 에서 정의된 Header 에 Key-Value 쌍과 Consumer 에서 정의된 Argument 의 Key-Value 쌍이 일치하면 Binding 됩니다.

KeyValue설명
x-matchallheader 의 모든 key-value 쌍 값과 argument의 모든 key-value 쌍이 일치할때만 Binding
x-matchanyheader 의 key-value 쌍 값과 argument의 하나의 key-value 쌍과 일치하더라도 Binding

3. [RabbitMQ 구성 요소의 세부 속성]

- Exchange 속성

  • Name : Exchange 이름
  • Type : Binding 전략(앞서 살펴본 4가지 전략)
  • Durability : 메세지 브로커가 시작될 때 남아있는지 여부
    - Durable : 메세지 브로커 서버가 재시작되어도 기존 Exchange 메세지가 저장되어 남아있음
    -Transient : 메세지 브로커 서버가 재시작되면 기존 Exchange 메세지가 모두 사라짐
  • Auto-delete : 마지막 Queue 연결이 해제되면 Exchange 삭제됨

이번 실습에서는 4가지 Binding 전략 중 일반적인 전략인 Direct Exchange 을 사용해보겠습니다.
자 그럼 이제부터 실제로 Spring 애플리케이션과 함께 RabbitMQ 을 구축해보겠습니다❗️


1. Spring RabbitMQ 설정하기

build.gradle RabbitMQ 의존성 추가하기

implementation 'org.springframework.boot:spring-boot-starter-amqp'

application.yml 설정값 추가하기

spring:
  rabbitmq:
    host: localhost // RabbitMQ host ip
    port: 5672 // RabbitMQ port
    username : guest // RabbitMQ 웹 관리 콘솔 아이디
    password: guest // RabbitMQ 웹 관리 콘솔 비밀번호

rabbitmq:
  queue:
    name: sample-queue // 사용할 queue 이름
  exchange:
    name: sample-exchange // 사용할 exchange 이름
  routing:
    key : key

2. docker을 이용한 RabbitMQ 서버 구동하기

docker run -d --name rabbitmq -p
5672:5672 -p 15672:15672 --restart=unless-stopped rabbitmq:management

포트 5672는 RabbitMQ 브로커(Broker) 연결, 포트 15672는 RabbitMQ 웹 관리 콘솔에 사용됩니다.
결국, RabbitMQ 또한 하나의 서버상에서 구동되는 시스템이기 때문에 이와 같이 Docker 을 이용해서 구축해줍니다.

해당 이미지는 실제RabbitMQ 웹 관리 콘솔창 이미지입니다.
콘솔창에서 RabbitMQ 에 대한 다양한 정보를 확인할 수 있으며 조작이 가능합니다 👨‍💻

3. RabbitMQConfig.class

RabbitMQ 의 정상적인 동작을 위해 설정 클래스를 만듭니다.

RabbitMqProperties.class

SpringBoot 어플리케이션에 RabbitMQ 을 구축하기 위해서는 필요한 설정값이 있습니다.
Host,Port,QueueName,ExchangeName 등 다양한 설정값은 위에서 살펴본바와 같이 application.yml 에 설정하면 되는데, 이번 실습에서 spring.rabbitmq 의 prefix 을 가지는 값들을 RabbitMqProperties 클래스 필드로 바인딩 한 후 사용하려고 합니다 ❗️

@ConfigurationProperties 어노테이션을 사용하는 방법에 익숙하지 않다면 [Spring] Spring 설정 파일에 있는 설정 값을 애플리케이션에서 활용하는 방법 (@Value,@ConfigurationProperties) 을 참고해주세요.

@ConfigurationProperties(prefix = "spring.rabbitmq")
@ConstructorBinding
@AllArgsConstructor
@Getter
public class RabbitMqProperties {
    private String host;
    private int port;
    private String username;
    private String password;
}
@ConfigurationPropertiesScan // class path 존재하는 모든 ConfigurationProperties Scan(필수❗️) 
@SpringBootApplication
public class RedisApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class, args);
    }
}

RabbitMqConfig.class

@RequiredArgsConstructor
@Configuration
public class RabbitMqConfig {

    private final RabbitMqProperties rabbitMqProperties;

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

    @Value("${rabbitmq.exchange.name}")
    private String exchangeName;

    @Value("${rabbitmq.routing.key}")
    private String routingKey;

    // org.springframework.amqp.core.Queue
    @Bean
    public Queue queue() {
        return new Queue(queueName);
    }

    /**
     * 지정된 Exchange 이름으로 Direct Exchange Bean 을 생성
     */
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(exchangeName);
    }

    /**
     * 주어진 Queue 와 Exchange 을 Binding 하고 Routing Key 을 이용하여 Binding Bean 생성
     * Exchange 에 Queue 을 등록한다고 이해하자
     **/
    @Bean
    public Binding binding(Queue queue, DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(routingKey);
    }

    /**
     * RabbitMQ 연동을 위한 ConnectionFactory 빈을 생성하여 반환
     **/
    @Bean
    public CachingConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setHost(rabbitMqProperties.getHost());
        connectionFactory.setPort(rabbitMqProperties.getPort());
        connectionFactory.setUsername(rabbitMqProperties.getUsername());
        connectionFactory.setPassword(rabbitMqProperties.getPassword());
        return connectionFactory;
    }

    /**
     * RabbitTemplate
     * ConnectionFactory 로 연결 후 실제 작업을 위한 Template
     */
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter());
        return rabbitTemplate;
    }

    /**
     * 직렬화(메세지를 JSON 으로 변환하는 Message Converter)
     */
    @Bean
    public MessageConverter jackson2JsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

RabbitMqConfig 클래스 내부에 각 부분에 대한 설명이 있으니 참고바랍니다.


4. RabbitMqService

/**
 * Queue 로 메세지를 발핼한 때에는 RabbitTemplate 의 ConvertAndSend 메소드를 사용하고
 * Queue 에서 메세지를 구독할때는 @RabbitListener 을 사용
 *
**/
@Slf4j
@RequiredArgsConstructor
@Service
public class RabbitMqService {

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

    @Value("${rabbitmq.exchange.name}")
    private String exchangeName;

    @Value("${rabbitmq.routing.key}")
    private String routingKey;

    private final RabbitTemplate rabbitTemplate;

    /**
     * 1. Queue 로 메세지를 발행
     * 2. Producer 역할 -> Direct Exchange 전략
    **/
    public void sendMessage(MessageDto messageDto) {
        log.info("messagge send: {}",messageDto.toString());
        this.rabbitTemplate.convertAndSend(exchangeName,routingKey,messageDto);
    }

    /**
     * 1. Queue 에서 메세지를 구독
    **/
    @RabbitListener(queues = "${rabbitmq.queue.name}")
    public void receiveMessage(MessageDto messageDto) {
        log.info("Received Message : {}",messageDto.toString());
    }
}

실제로 현재 개발 트랜드에서 RabbitMQ 는 서로 다른 어플리케이션 끼리 메세지를 주고 받을 때 사용되지만, 여기서는 간단한 실습을 목표로 하기에 하나의 어플리케이션에 ProducerConsumer 을 구현했습니다.

RabbitMqService 클래스는 sendMessagereceiveMessage 메소드로 구성되는데,
전자가 Producer 의 역할을 후자가 Consumer 의 역할을 수행합니다❗️

저희는 application.yml 에 사용할 Queue,Exchange 그리고 둘을 바인딩 시켜줄 Routing key 를 설정하였습니다.
실제 Producer 에서 메세지를 발행할 때는 RabbitTamplateconvertAndSend 메소드를 사용합니다👨‍💻

이렇게 메세지를 발행하면 Direct Exchange 전략에 따라 주어진 Routing Key 로 바인딩된 Queue 로 메세지가 들어가게 되고, 이는 해당 Queue 을 구독하는 Consumer(어플리케이션) 으로 들어가게 됩니다.


5. RabbitMqController

@Slf4j
@RequiredArgsConstructor
@RestController
public class RabbitMqController {
    private final RabbitMqService rabbitMqService;

    @PostMapping("/send/message")
    public ResponseEntity<String> sendMessage(
            @RequestBody MessageDto messageDto
    ) {
        this.rabbitMqService.sendMessage(messageDto);
        return ResponseEntity.ok("Message sent to RabbitMQ");
    }
}

RabbitMqController 로 부터 클라이언트로부터 Http 요청을 받아 간단한 테스트를 진행해보겠습니다 ❗️


6. TEST

정상적으로 RabbitMq 가 잘 동작하는 것을 확인할 수 있습니다 🔥
또한 관리자(Admin) 은 15672 포트에서 돌아가는 RabbitMq 웹 관리 콘솔창에서 이와 관련된 여러가지 정보(통계 등) 을 확인할 수 있습니다 🧑‍🔧


참고

[RabbitMQ] 기초 개념
[Spring Boot] RabbitMQ 연동하기
RabbitMQ - 레빗엠큐 개념 및 동작방식, 실습

profile
비즈니스가치를추구하는개발자

0개의 댓글