1) RabbitMQ
- 메세지 브로커 : 데이터(메세지)를 송신자(프로듀서)로부터 수신자(컨슈머)에게 전달하는 중간 매개체 역할
- RabbitMQ는 메세지를 큐(queue)에 저장하고 필요할 때 적절한 수신자에게 전달함.
- 역할
- 비동기 처리 : 데이터를 비동기적으로 처리하여 시스템의 응답성 증가
- 부하 분산 : 여러 소비자에게 메시지를 분산시켜 시스템의 부하 분산
- 내결함성 : 메시지를 안전하게 저장하여 시스템 장애 시 데이터 손실 방지
- 장점
- 신뢰성(Reliability)
- 메세지 지속성 : 메세지를 디스크에 저장해 장애 발생시에도 손실 방지
- 확인 메커니즘 : ACK 메커니즘을 지원하여 안정적인 메세지 전달 보장
- 유연성(Flexibility)
- 다양한 메세지 패턴 지원 : 단일 소비자, 다중 소비자, 라운드 로빈, 팬아웃, 주제 기반 등
- 프로토콜 지원 : 기본적으로 AMQP 사용, 이외에도 STOMP, MQTT 등 여러 프로토콜 지원
- 확장성(Scalability)
- 클러스터링 : 여러 노드로 구성된 환경에서 높은 가용성과 부하 분산 제공
- 분산 아키텍처 : 페더레이션(Federation)을 통해 여러 인스턴스 간에 메세지 전달함으로 분산된 메시징 시스템 구축
- 관리 및 모니터링(Manageability and Monitoring)
- 관리 인터페이스 : 웹 기반 관리 인터페이스로 큐, 익스체인지, 바인딩 등 쉬운 관리 가능
- 플러그인 시스템 : 다양한 플러그인으로 기능 확장 가능
- 성능(Performance)
- 잘 구성된 RabbitMQ는 높은 메시지 처리량을 지원하여 대규모 애플리케이션에서도 효율적 운영 가능
- 신뢰성(Reliability)
- 단점
- 설정 및 운영 복잡성(Setup and Operational Complexity)
- 복잡한 설정 : RabbitMQ의 초기 설정 다소 복잡한데, 클러스터링 및 분산 환경에서는 더 복잡함.
- 운영 관리 : 대규모 환경에서 RabbitMQ를 운영 및 관리하는 데에 추가적인 노력이 필요함.
- 성능 문제(Performance Issues)
- 메세지 브로커 오버헤드 : RabbitMQ는 모든 메세지를 중앙 브로커를 통해 전달해서
높은 트래픽 상황에서는 브로커의 오버헤드가 발생할 수 있음. - 대규모 메세지 처리 : 초 대규모 메세지 처리시 성능 저하 발생 → 클러스터링 및 최적화 필요함.
- 메세지 브로커 오버헤드 : RabbitMQ는 모든 메세지를 중앙 브로커를 통해 전달해서
- 운영 비용(Operational Costs)
- 리소스 소비 : RabbitMQ는 메모리/CPU 자원 많이 소비할 수 있기 때문에,
원활한 운영을 위한 충분한 리소스 제공이 필요함. - 모니터링/유지보수 : 지속적인 모니터링과 유지보수 필요해 추가적인 인력 및 비용 발생 가능성 존재
- 리소스 소비 : RabbitMQ는 메모리/CPU 자원 많이 소비할 수 있기 때문에,
- 제한된 메세지 크기(Limited Message Size)
- 메세지 크기 제한 : RabbitMQ는 매우 큰 메세지 처리 제한이 있을 수 있기 때문에,
대용량 파일 전송에 적합하지 않을 수 있음.
- 메세지 크기 제한 : RabbitMQ는 매우 큰 메세지 처리 제한이 있을 수 있기 때문에,
- 러닝 커브(Learning Curve)
- 학습 필요성 : RabbitMQ를 학습하는 동안 시간 소요 가능성 존재
- 설정 및 운영 복잡성(Setup and Operational Complexity)
2. RabbitMQ 기본 구성 요소
1) Message
RabbitMQ를 통해 전달되는 데이터 단위
예) 사용자 등록 정보, 주문 내역 등
2) Producer
메세지를 생성하고 RabbitMQ에 보내는 역할
예) 웹 애플리케이션이 사용자 등록 정보를 RabbitMQ에 보내는 경우 프로듀서가 됨.
3) Queue
메세지 저장소
- 메세지는 큐에 저장되어 있다가 소비자에게 전달됨.
- FIFO 방식으로 메세지 처리함.
4) Cousumer
큐에서 메세지를 가져와 처리하는 역할
예) 이메일 발송 서비스가 큐에서 사용자 등록 정보를 가져와 환영 이메일을 보내는 경우 컨슈머가 됨.
5) Exchange
메세지를 적절한 큐로 라우팅하는 역할
- 프로듀서가 메세지를 큐가 아닌 익스체인지로 보내면, 익스체인지는 메세지를 적절한 큐로 전달함.
3. AMQP
- RabbitMQ는 AMQP(Advanced Message Queuing Protocol)를 사용함.
- AMQP : 메시지 브로커를 위한 프로토콜로, 메시지의 생성, 전송, 큐잉, 라우팅 등을 표준화하여
메시지 브로커가 상호 운용될 수 있게 함. - Protocol : 컴퓨터 네트워크에서 데이터를 주고받기 위한 일련의 규칙과 절차
⇒ 컴퓨터나 장치들이 서로 통신할 때 어떻게 소통해야 하는지를 정해 놓은 약속 - 주요 개념
- Message : 전송되는 데이터 단위
- Queue : 메시지를 저장하고 전달하는 구조
- Exchange : 메시지를 큐로 라우팅하는 역할
- Binding: 익스체인지와 큐를 연결하는 설정으로, 바인딩을 통해 메세지가 어느 큐로 전달될지 정의함.
(큐의 이름 = 바인딩 이름)
- 익스체인지 유형
- 메세지 브로커가 메시지를 교환기에서 큐로 라우팅하는 방식
- 익스체인지는 다양한 방식으로 메세지를 라우팅할 수 있으며, 주로 메시지의 라우팅 키와 바인딩 키 또는 패턴을 기반으로 작동함.
- 라우팅 키가 정확히 일치하는 큐로 메세지를 전달함.
- 예) 라우팅 키가 error인 메시지는 error라는 바인딩 키를 가진 큐로 전달됨.
- 라우팅 키의 패턴을 사용하여 메세지를 라우팅함.
- 패턴에는 와일드카드 * (단어 하나)와 # (0개 이상의 단어)가 사용됨.
- 예) 라우팅 키가 quick.orange.rabbit인 메세지는 바인딩 키가 *.orange.*인 큐로 전달됨.
- 라우팅 키를 무시하고 교환기에 바인딩된 모든 큐로 메세지를 브로드캐스트함.
- 모든 바인딩된 큐로 메세지가 전달됨.
- 라우팅 키 대신 메시지의 헤더를 기반으로 메시지를 라우팅함.
- 헤더 값과 바인딩된 헤더 값이 일치하는 큐로 메시지를 전달함.
4. 실습
1) RabbitMQ 설치
- 도커로 RabbitMQ 설치
docker run -d --name rabbitmq -p5672:5672 -p 15672:15672 --restart=unless-stopped rabbitmq:management
- RabbitMQ 큐가 안 생기길래 봤더니 아예 연결조차 안됨.
- m1 이슈가 있다길래 homebrew로 해보다가 RabbitMQ 관리 플러그인 활성화했더니 연결됨.
혹시나 해서 brew 삭제하고 도커로도 다시 해봤더니 이것도 연결됨!
/opt/homebrew/sbin/rabbitmq-plugins enable rabbitmq_management
- localhost:15672 - guest/guest로 로그인
2) Order Application
- Order 프로젝트 생성
- application.properties
spring.application.name=order
message.exchange=market
message.queue.product=market.product
message.queue.payment=market.payment
spring.rabbitmq.dynamic=true
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
- OrderApplicationQueueConfig
package com.market.order;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OrderApplicationQueueConfig {
@Value("${message.exchange}")
private String exchange;
@Value("${message.queue.product}")
private String queueProduct;
@Value("${message.queue.payment}")
private String queuePayment;
// Exchange
@Bean public TopicExchange exchange() {return new TopicExchange(exchange);}
// Queue
@Bean public Queue queueProduct() { return new Queue(queueProduct); }
@Bean public Queue queuePayment() { return new Queue(queuePayment); }
// Binding
@Bean public Binding bindingProduct() {
return BindingBuilder
// 어느 큐로 이동할 건지
.bind(queueProduct())
// 익스체인지
.to(exchange())
// 바인딩 이름 (큐의 이름과 같아야 함)
.with(queueProduct);
}
@Bean public Binding bindingPayment() { return BindingBuilder.bind(queuePayment()).to(exchange()).with(queuePayment); }
}
- OrderController
더보기
package com.market.order;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping("/order/{id}")
public String order(@PathVariable("id") String id) {
return "Order Complete";
}
}
- OrderService
package com.market.order;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class OrderService {
@Value("${message.queue.product}")
private String productQueue;
@Value("${message.queue.payment}")
private String paymentQueue;
private final RabbitTemplate rabbitTemplate;
public void createOrder(String orderId) {
rabbitTemplate.convertAndSend(productQueue, orderId);
rabbitTemplate.convertAndSend(paymentQueue, orderId);
}
}
- localhost/order/1
- 익스체인지
- 큐 - 생성된 큐 들어가보면 바인딩 키(Bindings)랑 메세지(Get messages) 확인 가능
3) Payment Application
- Payment 프로젝트 생성
- application.properties
spring.application.name=payment
message.queue.payment=market.payment
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
- PaymentEndpoint
package com.market.payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class PaymentEndpoint {
@Value("${spring.application.name}")
private String appName;
@RabbitListener(queues = "${message.queue.payment}")
public void receiveMessage(String orderId) {
log.info("receive orderId:{}, appName : {}", orderId, appName);
}
}
컨슈머가 없어서 쌓여있던 메세지가 Payment Consumer 실행되자마자 처리됨.
4) Product Application
- Product 프로젝트 생성
- application.properties
spring.application.name=product
message.queue.product=market.product
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
- ProductEndpoint
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ProductEndpoint {
@Value("${spring.application.name}")
private String appName;
@RabbitListener(queues = "${message.queue.product}")
public void receiveMessage(String orderId) {
log.info("receive orderId:{}, appName : {}", orderId, appName);
}
}
- 컨슈머가 Round Robin으로 메세지 전달 받는 것을 확인하기 위해 Edit Configuration…에서
VM options 추가해서 2개의 Product를 생성함.
- Order에서 요청할 때마다 번갈아가며 메세지 수신 받는 걸 확인할 수 있음.
- RabbitMQ > Overview > Nodes : RabbitMQ의 어느 정도의 성능을 확인할 수 있음.
728x90
'TIL' 카테고리의 다른 글
TIL21. 물류 관리 및 배송 시스템 프로젝트 시작!! (0) | 2025.03.12 |
---|---|
TIL20. Kafka (0) | 2025.03.11 |
TIL18-2. 캐싱 (0) | 2025.03.07 |
TIL18-1. Session Clustering, 리더보드 만들기 (1) | 2025.03.07 |
TIL17. Redis (2) SpringBoot로 Redis 사용해보기 + 실습 (0) | 2025.03.06 |