이번 포스팅에서는 데이터 전송을 비동기적으로 처리하고, 서버 간 데이터를 전달할 때 효율성을 높일 수 있다는 장점 덕분에
분산 시스템이나 MSA(마이크로서비스 아키텍쳐)에서 서비스간의 통신을 원활히 처리하기 위해 사용되는 메시지 큐(Message Queue)와 브로커(Broker)에 대해 알아보자.
MOM(Message-Oriented Middleware)
은 분산 시스템 내에서 서로 다른 애플리케이션 간에 메시지를 비동기적으로 주고받을 수 있게 하는 미들웨어이다. 애플리케이션 간의 데이터 전송을 돕고 통신을 중재하는 역할을 하며,메시지 큐
,메시지 브로커
,이벤트 브로커
와 같은 다양한 형태로 제공된다. MOM은 특히 비동기적 처리와 비동기적 통신이 필요한 경우에 효과적이다.
※ 비동기
는 어떤 작업이 완료될 때까지 기다리지 않고, 그 작업이 끝나면 나중에 결과를 받는 방식이다. 특정 작업을 실행하더라도 기다리지 않고 그동안 다른 일을 계속할 수 있도록 하는 것이 비동기의 핵심이다.
확장성: 여러 서비스가 동시에 연결되어도 쉽게 확장할 수 있으며, 메시지 전달의 병렬 처리가 가능하여 이를 통해 대규모 트래픽을 효율적으로 처리할 수 있다.
분산 시스템 통합: MOM은 서로 다른 기술 스택을 사용하거나 분산된 환경에 있는 시스템들을 연결하는 데 사용되어 서로 다른 언어나 플랫폼으로 작성된 애플리케이션들 간에도 메시지 전달을 가능하게 해 분산되어 있는 시스템 상호작용이 가능하게 한다.
메세지 큐
브로커
메세지 큐
: 메시지 큐는 데이터를 전송할 때Producer(송신자)
와Consumer(수신자)
가 직접적으로 연결되지 않고 Producer에서 전송된 데이터가 저장되는큐(Queue)
형태의 Producer와 Consumer 사이의 메세지 전달 매개체이다.
Producer가 메세지 큐에 데이터를 넣어두면 Consumer가 데이터 처리가 가능할 때 메세지 큐에서 데이터를 꺼내는 방식이기 때문에 송신자와 수신자가 서로 독립적으로 작업할 수 있게 해준다.
개인적으로 메세지 큐를 현실의 사례에 쉽게 비유하자면 '우체통'이라고 생각하면 될 것 같다. 고객이 보내고 싶은 우편물을 직접 상대방에게 보내는 것이 아니라 우체통에 넣어 놓으면 우체국 측에서 차후에 우체통에서 해당 우편물을 수거하여 우편물을 보내주는 역할을 하는 것처럼 데이터를 보내주는 송신자(Producer)
역할을 하는 서비스가 전달하고 싶은 데이터를 메세지 큐에 저장하면 데이터를 받아 사용해야 하는 수신자(Consumer)
역할의 서비스가 메세지 큐에서 데이터를 꺼내어 사용하는 구조이다.
아주 간단하게 말하자면 메시지 큐는 단순히 메시지(데이터)를 줄 세워서 대기시키는 큐 형태의 자료구조라고 이해하면 된다.
큐(Queue)
: 송신자가 보낸 데이터(메시지)가 순서대로 저장되는 데이터 구조로 큐에 저장된 메시지는 일반적으로 FIFO(First In, First Out) 방식으로 처리된다.
메시지(Message)
: 송신자가 보내는 단위 데이터이다. 메시지에는 수신자가 알아야 할 정보를 포함하며, 주로 JSON, XML 등의 형태로 저장된다.
송신자(Producer)
: 메시지를 생성하여 큐에 보내는 주체이다.
수신자(Consumer)
: 큐에서 메시지를 꺼내어 처리하는 주체이다.
비동기 처리: 메시지 큐에 메시지를 저장한 뒤 송신자는 그 이후의 과정은 아예 신경 쓰지 않고 작업을 완료했다고 간주할 수 있으며, 수신자는 언제든지 처리가 가능할 때 큐에서 메시지를 꺼내 처리할 수 있어 비동기적 처리가 쉽다.
부하 분산: 수신자가 여러 개일 때 메시지 큐는 부하를 분산하여, 각 수신자가 부담을 적게 받고 작업할 수 있다.
서비스 간 결합도 낮추기: 송신자와 수신자가 직접 연결되지 않기 때문에 시스템 간 결합도와 종속성이 낮아지고, 수신자가 나중에 추가되거나 변경되더라도 송신자는 수정할 필요가 없다.
데이터 유실 방지: 송신자가 데이터를 보내도 수신자가 처리할 준비가 안 되어 있을 때 메시지 큐에 저장해 두면 데이터 유실을 방지할 수 있다.
RabbitMQ의 Queue
Kafka의 topic
ActiveMQ의 Queue
브로커(Broker)
: 메시지 브로커(Message Broker)는 메세지 큐와 같은 채널의 상위 개념이자 Producer와 Consumer 사이에서메시지 전송 및 관리를 담당하는 중개 시스템
으로, 브로커는 데이터 전송의 안정성과 효율성을 높이기 위해 여러 수신자에게 메시지를 전달하거나 특정 조건에 따라 메시지를 필터링 및 분배하는 기능을 제공한다.
메시지 브로커는 큐뿐만 아니라, 게시-구독(Pub/Sub) 같은 다양한 통신 패턴을 지원하여 메시지를 분배하거나 필요한 수신자에게만 전달하는 기능도 포함한다.
메시지 중계 및 전달: 브로커는 클라이언트나 서버 간의 메시지를 중계하고, 메시지가 올바른 수신자에게 전달되도록 한다.
메시지 저장 및 관리: 메시지를 일시적으로 저장하고, 큐에서 메시지를 꺼내서 처리하거나 전달할 수 있도록 관리한다. 예를 들어, 메시지 큐 시스템에서는 메시지를 보관한 후 소비자가 이를 처리할 때까지 기다린다.
메시지 라우팅: 메시지를 어떤 경로로 전달할지를 결정하는 라우팅 역할을 한다.
예를 들어, RabbitMQ나 ActiveMQ는 각기 다른 큐로 메시지를 라우팅할 수 있다.
RabbitMQ
: 메시지 큐 시스템의 일종으로, 메시지를 큐에 저장하고 이를 소비자가 가져가도록 중계한다.
Apache Kafka
: 대규모 분산 시스템에서 사용되는 메시지 브로커로, 데이터를 일관성 있게 저장하고 여러 소비자가 읽을 수 있도록 메시지를 분배한다.
ActiveMQ
: Java 기반의 메시지 브로커로, 다양한 메시징 프로토콜을 지원하고 큐를 사용하여 메시지를 전달한다.
Amazon SQS (Simple Queue Service)
: AWS에서 제공하는 메시지 큐 서비스로, 메시지 브로커 역할을 하며 확장성 있는 메시징 시스템을 제공한다.
하지만 여기서 한 가지 의문점이 생길 수 있다. 전통적인 방식대로 데이터를 생성한 서비스에서 각각 데이터를 활용하는 서비스들의 API를 호출해주면 되는 거 아닌가?
하지만 이러한 API 호출은 몇 가지 문제점을 가지고 있다.
만약 고객이 주문을 하였는데 이 방식은 동기
적으로 처리되기 때문에 속도도 느리고 만약 처리 중간에 서버 오류로 인하여 데이터가 유실되며 그 이후의 서비스들은 스스로 진행이 불가능하다.
현재 구조에서는 주문 서비스가 너무 많은 역할을 하고 있고 이로 인하여 주문 서비스에 모든 서비스가 의존적이라서 주문 서비스의 장애는 곧 모든 서비스의 장애로 이루어진다는 문제점이 존재한다.
하지만 Kafka, RabbitMQ 등의 브로커를 도입한다면 주문 서비스에서 주문이 생성되어 이벤트가 발생하면 각 서비스에서 앞 순서의 서비스를 기다리지 않고, 다른 서비스의 장애에 영향을 받지도 않고 각각 데이터를 꺼내가 비동기
적으로 각 서비스를 수행할 수 있다.
이러한 방식에는 각 서비스의 책임은 각 서비스들 본인들만 진다는 특징이 존재한다.
서비스 A가 자신의 서비스를 수행하고 카프카같은 브로커에게 데이터를 넘기기만 하면 데이터를 다른 서비스에 전달하는 일은 브로커가 알아서 해주기 때문에 그 이후의 일은 서비스 A는 알 필요도, 신경쓸 필요도 없다.
API 통신 대신 메시지 큐(MQ)나 메시지 브로커를 사용하는 이유를 정리하자면 다음과 같다.
비동기 처리 지원
API 통신은 동기 방식으로, 한 서비스가 요청을 보내면 상대방이 응답을 보낼 때까지 기다려야 한다.
반면, 메시지 큐나 메시지 브로커를 통해 메시지를 전달하면 비동기 방식으로 처리할 수 있다. 요청을 보내는 서비스는 메시지를 브로커에 넣고 바로 다른 작업을 할 수 있다. 상대방이 이 메시지를 나중에 꺼내 처리하더라도 원활히 작동한다.
확장성과 부하 분산
메시지 큐는 여러 개의 소비자(컨슈머)를 통해 하나의 작업을 동시에 처리할 수 있는 구조를 갖추고 있어, 확장이 용이하다.
예를 들어, 이벤트 처리와 같은 고부하 작업이 발생할 때 컨슈머 수를 늘려 부하를 분산할 수 있다. API 통신 방식은 각 서비스가 동시에 처리할 수 있는 요청 수에 제약이 있을 수 있어 부하 분산이 어렵다.
서비스 간 독립성 유지로 장애 전파 최소화
메시지 큐는 서비스 간 결합도를 낮춰 서로 독립적으로 동작할 수 있도록 한다.
만약 서비스 A가 서비스 B와 API 통신을 통해 상호작용할 경우, 서비스 B가 중단되면 A도 기능에 문제가 생길 수 있다. 하지만 메시지 브로커를 사용하면, 서비스 B가 일시적으로 중단되어도 A는 메시지를 브로커에 넣고 계속 운영할 수 있으며, B가 복구되면 이어서 메시지를 꺼내어 처리할 수 있다.
다양한 수신자에게 동시에 전달 가능
메시지 브로커를 사용하면 하나의 메시지를 여러 수신자가 받아서 처리할 수 있다.
예를 들어, Kafka의 경우 특정 이벤트를 여러 서비스가 동시에 구독하여 처리할 수 있어, 로그 분석, 알림, 실시간 통계 등의 기능을 동시에 구현할 수 있다. 반면 API 통신 방식은 일대일 방식으로 주로 사용되어 여러 수신자에게 같은 메시지를 전송하려면 요청을 반복해야 한다.
재처리 및 메시지 보관
Kafka와 같은 메시지 브로커는 메시지를 오래 보관하고, 특정 시점으로 돌아가 다시 소비할 수 있는 기능을 제공한다.
이를 통해 오류가 발생했을 때 메시지를 다시 가져와 재처리하거나, 특정 시점 이후의 메시지를 새로 추가된 서비스에 처리하게 할 수 있다. 반면 API 통신은 이런 기능을 기본적으로 지원하지 않기 때문에, 이와 같은 기능을 구현하려면 추가적인 개발이 필요하다.
이와 같이 브로커를 사용하면 API 통신의 동기적 제약과 결합성 문제를 해결하면서도 확장성과 장애 복구 능력이 뛰어난 시스템을 구축할 수 있다는 장점이 존재한다.