목차
각 단계별 메시지 전송 예제 살펴보기
각 단계별 Step Tutorial 강의로 단순 메시지 전송부터 Work Queue, Pub/Sub, Routing, Dead Letter Queue 등에 해당하는 샘플 코드들을 작성하면서 각 개념에 대해서 이해하도록 한다.
각 단계별 예제는 Git (https://github.com/villainscode/HelloMessageQueue)의 Branch 별로 총 12개의 예제 코드들을 참고하여 따라할 수 있도록 구성하였다.
단순 메시지 전송 (Producer to Consumer)
메시지 발행자(Producer)가 큐에 메시지를 전달하면 소비자(Consumer)는 큐에 들어온 메시지를 소진한다.
튜토리얼 Step 1. 단순 메시지 전송 (Producer to Consumer)
-
서버 실행 : rabbitmq 가 인스톨된 위치에서 sbin 하위의 ./rabbitmq-server를 실행
- brew 인스톨 위치 찾기 : brew —prefix rabbitmq
-
실습 전용 사용자와 VHost 추가. 사용자 이름·비밀번호를 실제 환경에 맞게 정하고 코드에 비밀번호를 기록하지 않는다.
VHost와 권한의 역할
VHost는 RabbitMQ에서 사용자를 격리하는 기본 단위로 다음과 같은 역할을 한다 1. 리소스 격리: VHost는 큐, 익스체인지, 바인딩을 포함한 RabbitMQ 리소스를 격리한다. 각 VHost는 독립된 큐 및 익스체인지 세트를 가지므로, 서로 다른 VHost에 있는 큐와 익스체인지는 서로 영향을 미치지 않는다. 2. 멀티 테넌시(Multi-Tenancy): 여러 애플리케이션이나 사용자 그룹이 하나의 RabbitMQ 인스턴스를 사용할 때, 각 애플리케이션이 서로 간섭하지 않고 독립적으로 운영될 수 있도록 함. 3. 접근 제어: VHost 단위로 사용자 접근 권한을 부여할 수 있어, 특정 사용자나 애플리케이션이 특정 VHost에만 접근하도록 제한할 수 있다.VHost 권한의 세 가지 종류
1. Configure (.*): 익스체인지 및 큐의 설정 권한. 2. Write (.*): 큐에 메시지를 쓰는 권한. 큐 전송 3. Read (.*): 큐에서 메시지를 읽는 권한. 큐 소비 -
IntelliJ 프로젝트 생성
- rabbitmq 관련 의존성추가 (기존에는 Spring AMQP)
-
정상적으로 실행 되는지 테스트
- rest api 샘플 작성
-
application.yml 작성
spring: rabbitmq: host: localhost port: 5672 # AMQP 포트; 15672는 관리 UI의 일반적인 포트 username: ${RABBITMQ_USERNAME} password: ${RABBITMQ_PASSWORD} application: name: HelloWorldMessageQueue server: port: 8080 -
RabbitMQConfig.java 작성
- https://github.com/villainscode/HelloMessageQueue/blob/tutorial-step1/src/main/java/net/harunote/hellomessagequeue/step1/RabbitMQConfig.java
- Bean 생성
- Queue queue() : Queue 인스턴스를 생성하고, 애플리케이션이 사용할 큐를 정의, 메시지를 전달하고 처리하는 기본 큐 세팅
- QUEUE_NAME은 메시지가 쌓이고 처리될 큐의 이름을 정의
- 다음 인자로 넘길
false는 큐 자체가 durable하지 않다는 뜻이다. 서버 재시작 뒤 큐 정의가 유지되지 않는다. 큐를 durable하게 선언하는 것만으로 개별 메시지의 영속성과 발행 성공 확인까지 보장되지는 않는다.
- RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) : RabbitMQ와 통신하기 위한 템플릿 인스턴스 생성, 메시지 송수신용
- JdbcTemplate과 비슷하게, RabbitMQ와 상호작용하기 위한 간단한 API를 제공합니다. 주로 메시지 전송을 담당
- ConnectionFactory는 RabbitMQ와의 연결을 관리하는 객체로, rabbitTemplate에 주입하여 메시지를 전송할 때 사용할 연결을 제공
- 메시지를 전송하는 Sender가 rabbitTemplate.convertAndSend() 메서드를 사용해 큐에 메시지를 넣는 데 사용
- SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) :
- RabbitMQ 메시지를 비동기적으로 수신하기 위해 SimpleMessageListenerContainer 를 생성, 이 컨테이너가 특정 큐를 지속적으로 모니터링하고 매시지를 수신하면 지정된 리스너(MessageListenerAdapter)를 통해 처리
- ConnectionFactory는 RabbitMQ와 연결을 유지하며, 수신하는 메시지를 이 연결을 통해 가져옴
- setQueueNames(QUEUE_NAME) 메서드는 특정 큐 이름을 설정. 이 컨테이너는 코드에서 설명한 큐 네임인 helloQueue에서 수신되는 메시지를 모니터링
- setMessageListener(listenerAdapter)는 listenerAdapter를 설정하여, 메시지가 수신될 때 호출할 리스너를 지정
- MessageListenerAdapter listenerAdapter(Receiver receiver) : 수신한 메시지를 특정 클래스의 특정 메서드로 전달하는 어댑터, 인자로 전달된 메서드를 자동으로 호출
- receiver 객체는 메시지를 처리하는 역할을 하는 빈이며, receiveMessage 메서드를 호출
- MessageListenerAdapter는 RabbitMQ에서 수신된 메시지를 특정 메서드에 전달할 수 있도록 해줌
- 이 경우, receiveMessage 메서드가 자동으로 호출되며, 메시지 내용을 인자로 받음
- Receiver 클래스의 receiveMessage 메서드가 메시지를 수신하여 처리할 수 있도록 설정 (RabbitMQ에서 수신된 메시지가 receiver.receiveMessage(String message) 메서드로 전달)
- Queue queue() : Queue 인스턴스를 생성하고, 애플리케이션이 사용할 큐를 정의, 메시지를 전달하고 처리하는 기본 큐 세팅
-
Message Sender 구현
-
Message Receiver 구현
-
REST API 호출 Endpoint 작성
-
메시지 전송 테스트 (인텔리제이 내장 http client)
###
GET http://localhost:8080/hello?q=world!!
<> 2024-11-20T201307.200.txt
###
POST http://localhost:8080/api/send
Content-Type: application/json
"MyQueue Hello World Test"
코드들은 https://github.com/villainscode/HelloMessageQueue/tree/tutorial-step1 확인
큐까지 메시지가 전달되는 경로
Producer가 직접 소비자 프로세스에 데이터를 보내는 것이 아니라 exchange에 발행하고, exchange가 routing key와 binding을 보고 큐로 보낸다. 기본 exchange를 쓰는 단순 예제에서는 큐 이름을 routing key로 사용할 수 있다. 소비자는 큐에서 메시지를 받아 처리한다. 큐에 메시지가 들어왔다는 것과 소비자가 업무 처리를 끝냈다는 것은 다른 상태다.
flowchart LR H[HTTP POST] --> P[RabbitTemplate 발행] P --> E[Exchange] E -->|routing key와 binding| Q[Queue] Q --> L[Listener 비동기 처리] L --> A[소비 확인 ack 또는 실패 처리]
Spring AMQP에서는 @RabbitListener가 메시지 수신 메서드를 간단히 정의한다. 아래는 글의 step1을 이해하기 위한 핵심 모양이다. 기존 Git 저장소의 클래스 이름·패키지와는 독립된 설명용 코드다.
@Configuration
class MessagingConfig {
@Bean
Queue helloQueue() {
return new Queue("hello.queue", true);
}
}
@Component
class MessageHandler {
@RabbitListener(queues = "hello.queue")
void receive(String body) {
System.out.println("received: " + body);
// 실제 업무 처리와 실패 시 재시도 정책을 여기에 연결한다.
}
}
new Queue("hello.queue", true)의 true는 큐 선언의 durable 속성이다. 메시지 발행의 영속성은 별도 속성이고, 브로커가 발행을 수락했는지 publisher confirm도 확인해야 한다. 전송 실패·라우팅 실패까지 고려하면 이 셋을 분리해서 검증한다. @RabbitListener 메서드는 리스너 컨테이너에서 호출되므로 HTTP 요청 스레드가 소비 완료를 기다리지 않는다.
성공과 실패의 경계
예를 들어 주문 요청이 HTTP로 들어와 큐에 기록되고, 소비자가 DB에 주문을 저장한다고 하자. HTTP에서 RabbitTemplate 호출이 반환됐더라도 메시지가 exchange에서 큐로 라우팅되지 않았다면 실제 소비가 일어나지 않는다. 발행자 확인(publisher confirm)은 브로커가 발행을 받아들였는지 확인하고, 소비자 확인(acknowledgement)은 소비자가 받은 배달을 처리했는지 확인한다. 두 확인은 서로 다른 방향의 메커니즘이다.
DB 저장을 끝낸 뒤 ack 직전에 소비자가 죽으면 RabbitMQ가 같은 메시지를 다시 전달할 수 있다. 따라서 주문 ID 같은 업무 키로 중복 삽입을 막는다. 계속 실패하는 메시지를 무한 재전달하면 정상 메시지 처리도 막힐 수 있으므로 재시도 횟수·지연·dead letter 처리를 설계한다.
| 관찰 | 먼저 볼 위치 |
|---|---|
| HTTP 성공, 소비 로그 없음 | 발행 confirm, exchange/binding/routing key, 큐 깊이 |
| 큐 깊이가 계속 증가 | 소비자 연결·권한·처리 속도·오류 로그 |
| 같은 메시지가 여러 번 처리됨 | ack 전에 실패했는지, 중복 처리 키가 있는지 |
| 재시작 뒤 메시지가 사라짐 | 큐 durable, 메시지 persistent, confirm 설정 |
실습에서 guestuser 같은 계정을 모두가 아는 비밀번호로 만들면 접근 범위가 넓어진다. 사용자·VHost를 분리하고, 애플리케이션에는 필요한 configure/write/read 권한만 준다. AMQP 5672와 관리 UI 15672는 목적이 다르며 운영에서는 TLS·방화벽·인증 정책을 적용한다. 예제의 GET /hello?q=...처럼 데이터를 바꾸는 HTTP GET 대신 POST를 사용하면 재요청·캐시 동작을 오해할 위험이 줄어든다.