본문 바로가기
공부

[BE] Publish & Subscribe 구조

by 꾸돼지 2025. 6. 4.
320x100

요즘 구현 중인 알림 기능을 만들며 조금 더 자세히 Pub/Sub 구조에 대해 고민하고 있다.

 

하나의 JVM 프로세스에서 호출되는 함수와 변수들은 메시지를 통해 내부 프로세스의 함수나 변수에 바로 접근할 수 있다.

JVM의 Method Area에서 상수와 함수들을, Heap에서 인스턴스들을 호출하고 그 결과를 바로 사용한다.

 

하지만 MSA와 같이 다른 프로세스로 분리된 서비스에선 이 역할을 네트워크를 통해 수행해야 한다.

보통 API 등을 호출하거나 위와 같이 메시지를 발행하고 소비하는 패턴을 사용해서 다른 서비스의 함수를 호출하게 된다.

 

내가 알림 기능을 이 구조로 변경해야겠다고 마음먹은 것은 과거에 있었던 운영자 기능의 공지사항 생성 시 알림 기능 때문이다.

당시에는 서버 사용자가 100명 미만이었기 때문에 공지사항을 생성하는 시점에 접속한 사용자(SSE 연결)에게 알림을 보냈다.

하지만 접속한 사용자의 수가 많아질수록 서버의 API 기능들의 응답 속도에 지장을 주기 좋은 구조로 보였다.

 

 

사실 이 구조는 Observer 패턴과 핵심 개념이 같다.

Publisher는 메시지 브로커에 메시지를 push한다. 메시지브로커는 자신에게 연결된 Subscriber들에게 메시지를 push하는 구조다.

이렇게 단방향으로 메시지의 전달이 이뤄지고, 절차적으로 기능의 수행이 이뤄지게 된다.

 

다만 메시지 브로커의 설정에 따라 push로 동작하기도 하고 pull로 동작하기도 한다.

pull의 경우 서버에서 해당 메시지 브로커에 있는 메시지를 직접 가져가서 소비하는 방식(Polling)이 된다.

 


나는 이 메시지 브로커로 Redis와 RabbitMQ를 써보았는데, 더 규모가 큰 시스템에선 Kafka를 쓰는 것 같다.

내가 써본 Redis와 RabbitMQ의 기능에 대해서만 정리를 해보려고 한다.

 

1. Redis

Redis의 pub/sub은 메시지 브로커 기능보단 실시간 메시지 전달에 초점을 맞춘 간단한 발행/구독 모델이다.

 

동작 방식

1. 발행자는 특정 채널에 메시지를 발행한다.

2. 해당 채널을 구독하고 있는 모든 구독자에게 메시지가 즉시 전달된다.

3. 메시지는 Redis 서버에 저장되지 않으므로 구독자가 연결되어 있지 않다면 해당 메시지는 유실된다.(fire-and-forget)

 

주요 특징

1. 빠른 속도: 인메모리 기반으로 동작하여 매우 빠른 메시지 전달 속도를 제공한다.

2. 단순함: 구현과 사용이 비교적으로 간단하다.

3. 메시지 유실 가능성: 구독자가 오프라인이거나, Redis 서버 장애 시 메시지 전달을 보장하지 않는다.(At-most-once delivery)

4. 메시지 큐 기능 부재: 메시지를 쌓아두고 순차적 처리하는 큐 기능이 없다. 다만 Redis는 Single-Thread로 유사하게 처리한다.

5. 복잡한 라우팅 부재: 다양한 조건에 따른 복잡한 메시지 라우팅 기능은 없다.

 

따라서 Redis를 메시지 브로커로 쓰는 경우는 메시지의 유실이 시스템에 치명적이지 않은 경우이다.

주로 실시간 채팅, 실시간 알림 등에 많이 사용된다고 한다.

 


 

2. RabbitMQ

RabbitMQ는 강력하고 다양한 기능을 제공하는 메시지 브로커이다. AMQP(Advanced Message Queuing Protocol)를 비롯한 여러 프로토콜을 지원하며, 메시지의 안정적인 전달과 복잡한 라우팅 시나리오에 적합하다.

 

동작 방식

1. 생산자는 메시지를 익스체인지에 발행한다.

2. 익스체인지는 바인딩 규칙 및 라우팅 키에 따라 메시지를 하나 이상의 큐로 라우팅한다. (Fanout Exchange)

3. 소비자는 큐에서 메시지를 가져와 처리한다.

4. 메시지는 큐에 저장되며, 소비자가 처리할 때까지 안전하게 보관된다. (디스크에 저장 가능)

 

주요 특징

1. 메시지 신뢰성: 메시지 확인(Acknowledgement, Ack) 메커니즘을 통해 메시지 전달을 보장한다. (At-least-once 또는 Exactly-once delivery 설정) 메시지 지속성을 통해 브로커가 재시작되어도 메시지가 보존된다.

2. 다양한 라우팅: Direct, Topic, Fanout, Headers 등 다양한 타입의 익스체인지를 통해 복잡한 메시지 라우팅을 로직을 구현할 수 있다.

  - Direct Exchange: 메시지의 라우팅 키와 큐가 Exchange에 바인딩될 때, 지정한 라우팅 키가 정확히 일치하는 큐로 메시지 라우팅

  - Topic Exchange: 메시지의 라우팅 키와 큐가 Exchange에 바인딩 될 때, 지정한 라우팅 패턴을 와일드카드 매칭

  - Fanout Exchange: 메시지의 라우팅 키를 무시하고, 해당 Exchange에 바인딩된 모든 큐로 메시지 복사하여 라우팅(브로드캐스팅)

  - Headers Exchange: 메시지의 라우팅 키 대신 메시지 헤더의 속성값 기준으로 라우팅

 

3. 메시지 큐잉: 메시지를 큐에 저장하여 비동기 처리, 작업 분배 등이 용이하다.

4. 유연성 및 확장성: 클러스터링을 통해 고가용성 및 확장성을 확보할 수 있다.

5. 관리 기능: 웹 기반 관리 콘솔 등 다양한 관리 도구를 제공한다.

 


 

메시지 브로커들의 전달 보장 수준(Delivery Guarantees)

1. At-least-once 최소 한 번 전달: 유실되지 않고 최소 한 번은 반드시 전달. 통신 지연으로 인한 중복 전달의 가능성도 존재

2. At-most-once 최대 한 번 전달: 메시지가 유실될 수 있지만 중복으로 전달하지 않음(Redis)

3. Exactly-once 정확히 한 번 전달: 유실되지 않고, 중복도 없이 정확히 한 번만 전달 및 처리(소비자에서 처리가 필요)

** RabbitMQ는 메시지 지속성, 큐 지속성, 소비자 확인 응답을 조합하여 At-least-once 전달에 효과적이다.

 

 

 


메시지 브로커 사용을 통해 시스템의 모듈간 결합도를 낮출 수 있고, 메시지의 전달이 쉬워지는 장점이 있다.

또한 적용이 매우 쉽고, 구조도 단순해져서 쉽게 이해할 수 있는 것 같다.

 

다만, 메시지 브로커에 장애가 발생했을 시, 중요한 기능이 전체적으로 마비되는 문제도 발생할 수 있다.

또한 잘 쓰기 위해서는 추가적인 학습이 필요하고, SI에서 사용하기엔 문서작업의 양이 엄청날 것 같은 두려움이 든다.

 

320x100