메세지 큐 (Message Queue)
원티드에서 진행하는 프리온보딩 Kotlin 및 백엔드 과정을 신청했다! 각 과정 별 준비해야하는 간단한 사전 과제가 있었는데, 그 중 하나가 메세지 큐에 대해 공부해보는 것이었다. 백엔드 직무 우대사항이나 자격요건에 메세지 큐를 필요로 하는 회사가 많았어서 언젠가 계속 정리해야지 했었는데 이 기회로 드디어 메세지 큐를 정리하게 되었다 ^ㅅ^!
내가 생각하기에 메세지 큐는 말 그대로 메세지들을 큐라는 자료 구조에 담아 관리하는 의미라고 생각된다.
MOM(Message Oriented Middleware: 메세지 지향 미들웨어)를 구현한 시스템을 MQ(Message Queue)라고 한다.
그렇다면 MOM이란 무엇일까?
메세지 지향 미들웨어 (MOM)
MOM은 비동기 메세지를 사용하는 프로그램 간 데이터 송수신을 의미한다.
기술은 지속적으로 변화하기 때문에, 기술에 사용되는 소프트웨어 시스템은 그러한 변화를 수용할 수 있어야 한다. 따라서 새 구성 요소를 통합하거나, 기존 구성 요소를 효과적으로 확장하는 것은 중요하다. 이러한 구성 요소를 통합하는 가장 쉬운 방법은 구성 요소를 동죵 요소로 다시 만드는 것이 아니라, 차이점과 상관 없이 구성 요소 간의 통신을 가능하게 해 주는 계층을 제공하는 것이다. 이 계층을 미들웨어 라고 한다.
MOM 기반 미들웨어는 분산 응용 프로그램 간에 메세지를 보내고 받으며 데이터를 전달하고 교환할 수 있게 해준다. 더 자세히 설명하자면, 클라이언트는 MOM 시스템을 통해 API를 호출해 메세지 공급자(Message Provider)가 관리하는 대상에게 메세지를 보낸다. 메세지를 보낸 후 클라이언트는 다른 작업을 계속해서 수행할 수 있다 (→ 저번 포스팅에서 다뤘던 비동기 개념!). 따라서 개별 구성 요소나 연결이 실패하더라도 중단 없이 계속해서 안정적으로 작동할 수 있다. 즉 메세지를 전달하는 과정에서 보관하거나 라우팅 및 변환을 할 수 있다.
- 보관 : 메세지의 백업을 유지함으로써 지속성을 제공. 덕분에 송수신 측은 동시에 네트워크 연결을 유지할 필요 없음
- 라우팅 : 미들웨어 계층 자신이 직접 메세지 라우팅이 가능하기 때문에, 하나의 메세지를 여러 수신자에게 배포 가능 (멀티캐스트)
- 변환 : 송수신 측의 요구에 따라 메세지를 변환할 수 있음
또한 메세징 공급자를 사용하여 클라이언트 간의 메세징을 중재할 경우 관리 인터페이스를 추가하여 성능을 모니터링 및 조정할 수 있다는 장점이 있다. 하지만 이러한 느슨한 연결 때문에 다음과 같은 단점들도 존재한다.
- 아키텍처에 외부 구성 요소인 메시지 전송 에이전트가 필요함
- 일반적으로 새로운 요소를 추가할 경우 시스템 성능이 저하되고 신뢰성이 떨어짐
- 시스템이 복잡해지기 때문에 관리가 어렵고 비용이 발생
- 애플리케이션 간의 통신은 본질적으로 동기지만, 메시지 기반 통신은 본질적으로 비동기이기 때문에 매커니즘 불일치가 발생
메세지 큐 (Message Queue)
먼저 메세지큐란 프로그램(프로세스) 간에 데이터를 교환할 때 사용하는 기술이고, 위에서 언급한 것처럼 MOM를 구현한 시스템이다. 서로 다른 프로세스 사이에 메세지를 교환할 때 AMQP(Advanced Message Queuing Protocol)을 이용한다.
AMQP (Advanced Message Queuing Protocol) 이란?
AMQP는 응용 계층의 MOM 표준으로, JMS(Java Message Service)와 비교된다. 먼저 JMS는 Java에서 지원하는 표준 API로, 다른 Java Application 간 통신이 가능하지만 다른 MOM(AMQP, SMTP)와는 통신할 수 없다.
반면 AMQP는 프로토콜만 일치한다면 다른 AMQP를 사용한 Application과도 통신이 가능하다.
즉 다시 말해, 메세지 큐는 처음에 생각했던 것 처럼 데이터를 메세지 형태로 생성, 전달과 수신이 가능하게끔 Queue 데이터 구조에 담아서 사용하는 것이다. 그렇다면 이러한 Message Queue가 왜 필요할까? 위의 이미지를 생각한다면 메세지 큐라는 중간 다리를 생략하고 바로 Producer - Consumer를 이어주는 게 더 효율적이지는 않을까라는 생각이 든다. 하지만 메세지 큐의 장점은 대용량 데이터를 처리할 때 두드러진다.
예를 들어, 1만 개의 요청이 발생했을 때 모든 요청을 전부 서버로 보내는 것이 아니라, 메세지 큐에 보내고, 서버 측에서는 처리하는 데 이상이 없을 정도의 요청들만 메세지 큐에서 가져와서 처리(일종의 버퍼 역할!)한다면 서버의 부담을 줄일 수 있다! 또한 이로인해 Producer - Consumer 간 속도가 다를 때, 한 쪽의 컴포넌트에서 장애가 발생했을 때에 대응이 가능하다.
이러한 장점에 대해서 더 구체적으로 정리하면 다음과 같다.
- 비동기(Asynchronous) : 데이터를 수신자에게 바로 보내지 않고 큐에 넣고 관리하기 때문에 나중에 처리 가능
- 비동조(Decoupling) : 애플리케이션과 분리할 수 있기 때문에 확장이 용이
- 탄력성(Resilience) : 일부가 실패하더라도 전체에 영향을 주지 않음
- 과잉(Redundancy) : 실패할 경우 재실행 가능 (장애 전파를 막을 수 있음)
- 보증(Guarantees) : 작업이 처리된 걸 확인할 수 있음
- 확장성(Scalable) : N:1:M 구조로 다수의 프로세스들이 큐에 메시지를 보낼 수 있음
그렇다면 이러한 메세지 큐는 어디에 사용될까?
애플리케이션 / 시스템 간 통신
위에서 언급한 것 처럼 메세지 큐를 이용하면 시스템 장애 발생 시 간편하게 처리가 가능하다. 일반적으로 서버가 갑자기 죽어버리거나, 서버 점검 등 다운타임이 발생하는 동안에는 요청을 주고 받을 수 없다. 서버에서 failover 처리를 해놓고 시스템이 정상적으로 돌아왔을 때 요청을 보내는 방법이 있지만, MQ를 사용하면 간편하게 처리가 가능하다.
Failover(페일오버)란?
페일오버는 장애 대비 기능으로, 시스템에 장애가 오면 미리 준비했던 다른 시스템(예비 시스템)으로 대체해서 운영하는 것이다.
- P(Producer)는 C(Consumer)에게 직접 요청을 하는 것이 아니라 MQ 에 메세지를 전달한다.
- C는 MQ 를 구독(Subscribe)하며, MQ 로부터 메세지를 수신해서 처리한다.
- C가 수신할 수 없는 상황이라면 메세지는 MQ 에 머물렀다가 C가 수신 가능할 때 메세지를 가져간다.
서버 부하가 많은 작업
이미지 처리, 비디오 인코딩, 빅데이터 등 대용량 데이터 처리와 같은 작업은 메모리, CPU를 많이 사용한다. 이러한 작업은 동시에 처리할 수 있는 양이 한정적이여서 무작정 요청을 보내 처리를 할 수 없다. 이럴경우, MQ를 사용하면 서버가 처리할 수 있는 양을 MQ에서 가져와 처리하면 된다.
부하분산이 필요한 작업
여러 C(Consumer)를 배치해, 원하는 메세지(데이터) 처리도 가능하다. 이러한 구조는 수평적 확장(Horizontal Scaling)에 유리하다.
따라서 메세지 큐는 채팅 서비스, 비동기 데이터 처리, 또는 모니터링, 로그, 이벤트 메세지 등의 대용량 데이터를 다룰 때 주로 사용된다.
이처럼 이번 포스팅에서는 메세지 큐에 대해서 알아보았다. 메세지 큐에 대해서 공부하고 나니, 실제로 메세지 큐를 어떻게 '이용' 할 수 있는지에 대해 궁금해졌다. 프리온보딩 백엔드 과정에서 메세지 큐를 다룰 예정이라고 하는데 (두근두근), 좀 더 공부해보고 블로그에도 정리해봐야겠다!
[참고]
https://docs.oracle.com/cd/E19148-01/820-0532/6nc919fai/index.html
https://sorjfkrh5078.tistory.com/291