CS

메시지 큐(Message Queue)

바디스 2022. 12. 15. 08:51

메시지 큐(Message Queue)란?

메시지 큐(Message Queue)는 프로세스 또는 프로그램 간에 데이터를 교환할 때 사용하는 통신 방법 중에 하나로, 메시지 지향 미들웨어(Message Oriented Middleware:MOM)를 구현한 시스템을 의미한다. 메시지 지향 미들웨어란 비동기 메시지를 사용하는 응용 프로그램들 사이에서 데이터를 송수신하는 것을 의미한다. 여기서 메시지란 요청, 응답, 오류 메시지 혹은 단순한 정보 등의 작은 데이터가 될 수 있다.

 

메시지 큐는 메시지를 임시로 저장하는 간단한 버퍼라고 생각하면 된다. 메시지를 전송 및 수신하기 위해 중간에 메시지 큐를 두는 것이다. 메시지 전송 시 생산자(Producer)로 취급되는 컴포넌트가 메시지를 메시지 큐에 추가한다. 해당 메시지는 소비자(Consumer)로 취급되는 또 다른 컴포넌트가 메시지를 검색하고 이를 사용해 어떤 작업을 수행할 때까지 메시지 큐에 저장된다. 각 메시지는 하나의 소비자에 의해 한 번만 처리될 수 있는데, 이러한 이유로 메시지 큐를 이용하는 방식을 일대일 통신이라고 부른다.

 

 

Message Queue는 언제쓰나요?

  • 메시지 큐는 소비자(Consumer)가 실제로 메시지를 어느 시점에 가져가서 처리하는 지는 보장하지 않는다.
  • 언젠가는 큐에 넣어둔 메시지가 소비되어 처리될 것이라고 믿는 것이다.
  • 이러한 비동기적 특성 때문에 메시지 큐는 실패하면 치명적인 핵심 작업보다는 어플리케이션의 부가적인 기능에 사용하는 것이 적합하다.

 

예를 들면, 이메일전송과 같은 작업이 있을 수 있다. 나는 이미 이메일을 전송했고, 실제 받는 사람이 읽을 때까지 시간은 걸리겠지만, 해당 작업이 완료처리 될 것을 우리는 알고있다.

 

블로그 포스팅 작업도 있다. 모든 블로그 사용자가 웹에 최적화되어 있거나, 용량이 작은 이미지만 업로드하진 않을 것이다. 블로그 사용자가 게시글에 업로드한 이미지의 용량이 매우 큰 경우 블로그 서비스의 응답 시간을 저해하지 않으면서 사용자들에게 유연성을 제공하는 방법으로, 사용자가 업로드한 모든 이미지를 게시 과정에서 즉각 처리하는 것이 아닌, 사후처리하며 최적화하는 방법이 있다. 사용자 경험에 약간의 영향을 미칠 수는 있지만, 최적화는 응용 프로그램에서 가장 중요한 것은 아니며 작업을 즉시 수행할 필요도 없다.

 

바로 실시간으로 처리되지 않아도 서비스에 크게 문제 없는 이런 작업에 MQ를 사용할 수 있다.
즉, MQ는 어느 정도의 응답 지연이 허용되며, 어플리케이션의 핵심 기능은 아닌 경우에 사용하는 것이 적합하다.

 

메시지 큐의 이점


비동기(Asynchronous)
메시지 큐는 생산된 메시지의 저장, 전송에 대해 동기화 처리를 진행하지 않고, 큐에 넣어 두기 때문에 나중에 처리할 수 있다. 여기서, 기존 동기화 방식은 많은 메시지(데이터)가 전송될 경우 병목이 생길 수 있고, 뒤에 들어오는 요청에 대한 응답이 지연될 것이다.

낮은 결합도(Decoupling)
생산자 서비스와 소비자 서비스가 독립적으로 행동하게 됨으로써 서비스 간 결합도가 낮아진다.

확장성(Scalable)
생산자 서비스 혹은 소비자 서비스를 원하는 대로 확장할 수 있기 때문에 확장성이 좋다.

탄력성(Resilience)
소비자 서비스가 다운되더라도 어플리케이션이 중단되는 것은 아니다. 메시지는 메시지 큐에 남아 있다. 소비자 서비스가 다시 시작될 때마다 추가 설정이나 작업을 수행하지 않고도 메시지 처리를 시작할 수 있다.

보장성(Guarantees)
메시지 큐는 큐에 보관되는 모든 메시지가 결국 소비자 서비스에게 전달된다는 일반적인 보장을 제공한다.

 

 

오픈소스 메세지 큐

RabbitMQ

더보기

 - AMQT 프로토콜을 구현해 놓은 프로그램입니다.

 - 신뢰성, 안정성과 성능을 충족할 수 있도록 다양한 기능을 제공합니다.

 - 유연한 라우팅: Message Queue가 도착하기 전에 라우팅 되며 플러그인을 통해 더 복잡한 라우팅도 가능합니다.

 - 클러스터링: 로컬네트워크에 있는 여러 RabbitMQ 서버를 논리적으로 클러스터링할 수 있고 논리적인 브로커도 가능합니다.

 - 관리 UI가 있어 편하게 관리 가능합니다.

 - 거의 모든 언어와 운영체제를 지원합니다.

 - 오픈소스로 상업적 지원이 가능합니다.

ActiveMQ

더보기

아파치 ActiveMQ는 풀 자바 메시지 서비스(JMS) 클라이언트와 함께 자바로 만든 오픈소스 메시지 브로커입니다. 이 시스템은 "엔터프라이즈 기능" - 이 경우는 하나 이상의 클라이언트와 서버간의 커뮤니케이션을 증진시키는 기능 - 을 제공합니다. JMS 1.1을 통해 자바 뿐만 아니라 다른 "교차언어"를 사용하는 클라이언트를 지원합니다. 커뮤니케이션은 컴퓨터 클러스터링 및 가상메모리, 캐쉬 그리고 저널 지속성을 제외한 어떤 데이터베이스를 JMS 지속성 제공자로 이용할 수 있는지 등의 특징들을 통해 운영됩니다.

 

 

주요 특성들

 - 다양한 언어 환경의 클라이언트들과 프로토콜을 지원하여, Java, C, C++, C#, Ruby, Perl, Python, 그리고 PHP 클라이언트들을 지원합니다.

 - OpenWire를 통해 고성능의 Java, C, C++, C# 클라이언트 지원합니다.

 - Stomp를 통해 C, Ruby, Perl, Python, PHP 클라이언트가 다른 인기있는 메시지 브로커들과 마찬가지로 ActiveMQ에 접근할 수 있습니다.

 - Message Groups, Virtual Destinations, Wildcards와 Composite Destinations를 지원합니다.

 - JMS 1.1과 J2EE 1.4를 완벽하게 지원하며, transient, persistent, transactional, 그리고 XA 메시징을 지원합니다.

 - Spring 지원으로 ActiveMQ는 Spring 애플리케이션에 매우 쉽게 임베딩될 수 있으며, Spring의 XML 설정 메커니즘에 의해 쉽게 설정됩니다.

 - Geronimo, JBoss 4, GlassFish, 그리고 WebLogic과 같은 인기있는 J2EE 서버들과 함께 테스트됩니다.

 - Inbound와 Outbound 메시징을 위한 JCA 1.5 Resource Adapter를 포함하여 ActiveMQ가 J2EE 1.4 호환 서버에 자동 배치됩니다.

 - In-VM, TCP, SSL, NIO, UDP, Multicast, JGroups, 그리고 JXTA Transport들과 같은 플러그인 가능한 전송 프로토콜들을 지원합니다.

 - 고성능의 저널을 사용할 때에 JDBC를 사용하여 매우 빠른 Persistnece를 지원합니다.

 - 고성능의 클러스터링, 클라이언트-서버, Peer 기반 통신을 지원을 위한 설계가 되어 있습니다.

 - REST API를 통해 웹기반 메시징 API를 지원합니다.

 - 웹브라우저가 메시징 도구가 될 수 있도록, Ajax를 통해 순수한 DHTML을 사용한 웹스트리밍 지원을 합니다.

 - In-memory JMS Provider로 사용될 수 있으며, 이는 JMS를 사용한 단위 테스트에 적합한 솔루션을 제공합니다.

 - STOMP, AMQP, MQTT, Openwire, SSL, and WebSockets

ZeroMQ

더보기

ZeroMQ는 메시징 라이브러리입니다. 많은 수고를 들이지 않고도 복잡한 커뮤니케이션 시스템을 설계할 수 있도록 해줍니다. ZeroMQ는 스스로 효율적으로 설명하기 위해 지금까지 많은 노력을 해왔습니다. 처음에는 '메세징 미들웨어'로 소개되었지만, 나중에는 '스테로이드를 맞은 TCP' 그리고 나중에는 '네트워크 스택의 새로운 레이어'라고 말합니다.

 

ZeroMQ(oMQ, zmq)는 임베디드 네트워킹 라이브러리 이지만, 동시성 프레임워크와 같은 역할을 합니다.

 

ZeroMQ는 in-process, inter-process, TCP, and multicast 처럼 다양한 방식으로 메시지를 전송하는 소켓을 제공합니다. fanout, pub-sub, tsak distribution, and request-reply와 같은 패턴으로 N-to-N 소켓을 연결할 수 있습니다. 클러스터 구조에서 충분한 속도를 제송합니다. 또한, 비동기 I/O 모델은 비동기 메시지 처리를 제공하는 확장 멀티 코어 애플리케이션을 제공합니다. 이것은 language API를 제공하며 대부분의 운영 체제에서 실행됩니다. ZeroMQ는 iMatix에서 만들어졌으며, LGPL 오픈소스 소프트웨어입니다.

 

퍼포먼스

ZeroMQ는 정말 빠릅니다. 대부분의 AMQP들 보다 단위가 다를 정도로 빠릅니다. 이러한 퍼포먼스는 다음과 같은 기술을 보유하기 있기때문에 가능한 것입니다.

 

 - AMQP처럼 과도하게 복잡한 프로토콜이 없습니다.

 - 신뢰성 있는 멀티캐스트나 Y-suite IPC 전송같은 효율적인 전송을 활용합니다.

 - 지능적인 메시지 묶음을 활용합니다. 이것은 ZeroMQ로 하여금 프로토콜 오버헤드뿐만 아니라 시스템 호출을 줄여서 TCP/IP를 효율적으로 사용하게 합니다.

 

단순성

API는 믿을 수 없을 정도로 간단합니다. 그렇기에 소켓 버퍼에 계속 '값을 채워' 주어야 하는 소켓 방식에 비교하면 메시지를 보내는 것이 정말로 단순합니다. ZeroMQ에서는 그냥 비동기 send 호출을 부르기만 하면, 메시지를 별도의 스레드의 큐에 넣고 필요한 모든 일을 해줍니다. 이러한 비동기 특성이 있기에 애플리케이션은 메시지가 처리되기를 기다리며 시간을 낭비하지 않아도 됩니다. ZeroMQ의 비동기 특성은 이벤트 중심의 프레임워크에도 최적입니다.

 

ZeroMQ의 단순한 와이어 프로토콜은 다양한 전송 프로토콜이 사용되는 요즘에 적합합니다. AMQP를 쓴다면 그 위에 또 다른 프로토콜 레이어를 쓴다는 것은 좀 이상하게 느껴집니다. ZeroMQ는 메세지를 그냥 BLOB(Binary Large Object)으로 보기에 메시지 인코딩 방식을 상관하지 않습니다. 단순히 JSON 메세지들을 보내던지, 아니면 BSON, Protocol Buffers나 Thrift 같은 바이너리 방식 메시지들도 괜찮습니다. 

 

확장성

ZeroMQ 소켓들은 저수준처럼 보이지만 사실은 다양한 기능들을 제공합니다. 예를 들어 하나의 ZeroMQ 소켓은 복수의 접점을 가질 수 있으며 그들 간에 자동으로 메시지 부하 분산을 수행합니다. 또는 하나의 소켓으로 복수의 소스에서 메시지들을 받아들이는 게이트 역할을 할 수도 있습니다.

Kafka

더보기

대용량의 실시간 로그 처리에 특화되어 설계된 메시징 시스템으로써 기존 범용 메시징 시스템대비 TPS가 매우 우수합니다. 단, 특화된 시스템이기 때문에 범용 메시징 시스템에서 제공하는 다양한 기능들은 제공되지 않습니다.

 

분산 시스템을 기본으로 설계되었기 때문에, 기존 메시징 시스템에 비해 분산 및 복제 구성을 손쉽게 할 수 있습니다.

 

AMQP 프로토콜이나 JMS API를 사용하지 않고 단순한 메시지 헤더를 지닌 TCP 기반의 프로토콜을 사용하여 프로토콜에 의한 오버헤드를 감소시켰씁니다.

 

Producer가 broker에게 다수의 메시지를 전송할 때 각 메시지를 개별적으로 전송해야하는 기존 메시징 시스템과는 달리, 다수의 메시지를 batch형태로 broker에게 한번에 전달할 수 있어 TCP/IP 라운드트립 횟수를 줄일 수 있습니다.

 

메시지를 기본적으로 메모리에 저장하는 기존 메시징 시스템과는 달리 메시지를 파일 시스템에 저장합니다.

 

- 파일 시스템에 메시지를 저장하기 때문에 별도의 설정을 하지 않아도 데이터의 영속성(durabiility)이 보장됩니다.

 

- 기존 메시징 시스템에서는 처리되지 않고 남아있는 메시지의 수가 많을 수록 시스템의 성능이 크게 감소하였으나, Kafka에서는 메시지를 파일 시스템에 저장하기 때문에 메시지를 많이 쌓아두어도 성능이 크게 감소하지 않습니다. 또한 많은 메시지를 쌓아둘 수 있기 때문에, 실시간 처리뿐만 아니라 주기적인 batch 작업에 사용할 데이터를 쌓아두는 용도로도 사용할 수 있습니다.

 

- Consumer에 의해 처리된 메시지(acknowledged message)를 곧바로 삭제하는 기존 메시징 시스템과는 달리 처리된 메시지를 삭제하지 않고 파일 시스템에 그대로 두었다가 설정된 수명이 지나면 삭제하도록 처리합니다. 처리된 메시지를 일정 기간동안 삭제하지 않기 때문에 메시지 처리 도중 문제가 발생하였거나 처리 로직이 변경되었을 경우 consumer가 메시지를 처음부터 다시 처리(rewind)하도록 할 수 있습니다.

 

기존의 메시징 시스템에서는 broker가 consumer에게 메시지를 push해 주는 방식인데 반해, Kafka는 consumer가 broker로부터 직접 메시지를 가지고 가는 pull 방식으로 동작합니다. 따라서 consumer는 자신의 처리능력만큼의 메시지만 broker로부터 가져오기 때문에 최적의 성능을 낼 수 있습니다.

 

- 기존의 push 방식의 메시징 시스템에서는 broker가 직접 각 consumer가 어떤 메시지를 처리해야 하는지 계산하고 어떤 메시지를 처리 중인지 트랙킹하였다면, Kafka에서는 consumenr가 직접 필요한 메시지를 broker로 부터 pull하므로 broker의 consumer와 메시지 관리에 대한 부담이 경감되었습니다.

 

- 메시지를 pull 방식으로 가져오므로, 메시지를 쌓아두었다가 주기적으로 처리하는 batch consumer의 구현이 가능합니다.

 

큐의 기능은 기존의 JMS의 AMQP 기반의 RabbitMQ(데이터 기반 라우팅, 페데레이션 기능 등)등에 비해서는 많이 부족하지만 대용량 메세지를 지원할 수 있는 것이 가장 큰 특징입니다. 특히 분산환경에서 용량 뿐 아니라, 복사본을 다른 노드에 저장함으로써 노드 장애에 대한 장애 대응성을 가지고 있기 때문에 용량에는 확실하게 강점을 지니고 있습니다.

MSA의 핵심 구성 요소 - Message Queueing

대부분의 마이크로서비스에서는 메시지 큐를 활용한 비동기 패턴을 사용하는 것을 볼 수 있다.

 

 

예를들어 다음과 같은 상황이 있다고 가정해보자.
User 서비스가 존재한다.

하지만 이 서비스가 사용자들이 많아진다고 해보자.

그럼 우리의 컨테이너 오케스트레이션 툴에 의해서 Scale-out 을 하게 되고 다음과 같이 두 개의 컨테이너가 생성되었다.

그럼 동일한 서비스의 데이터가 분산되어 저장되지 않을까? 그럼 동기화는 어떻게 시키지?
실제로 분산되어 저장된다.

결국 사용자는 API Gateway가 보내는 요청에 따라서 다른 데이터를, 무결하지 않은 데이터를 가져올 가능성이 존재하게 된다.

이 방식에는 여러 해결 방식이 존재한다.

  1. 하나의 서비스 인스턴스가 Scale-Out 된다 하더라도 동일한 데이터베이스를 사용하기
  2. Message Queue를 이용하기

하나의 서비스 인스턴스가 Scale-Out 된다 하더라도 동일한 데이터베이스를 사용하기

여러 인스턴스가 Scale-Out 되더라도 궁극적으로 해결하고자 하는 문제는 동일한 서비스이므로 하나의 데이터베이스를 사용하는 것은 큰 문제가 되지 않는다.


하지만 여러 인스턴스가 동일한 DB를 보고 작업을 하게 된다면 트랜잭션과 Isolation Level에 관한 문제가 발생하게 된다.

 

Message Queue를 이용하기

앞선 방식의 문제에서 트랜잭션과 고립 수준을 관리하는 것 보다 훨씬 간단한 방식인 데이터베이스 동기화를 수행하면 쉽게 해결할 수 있다.

메시지 큐잉 서비스를 한 줄로 정리하자면 다음과 같다.

 

마이크로서비스 구조에서 인스턴스가 확장되더라도 분산 시스템 및 데이터의 동기화 문제를 간단하게 해결할 수 있도록 하는 메시지 대기열 미들웨어이다.


주로 아파치 카프카 kafka나 래빗엠큐 Rabbit MQ와 같은 메시지 브로커를 사용해서 메시지를 구현한다.

이런 Message Queuing Server 를 구성하는 방법에는 크게 2가지가 존재한다.

 

  1. 인스턴스 각각의 데이터베이스를 운영하여 큐잉 서버에 구독
  2. 하나의 데이터베이스를 운영하며 중간에 큐잉 서버를 두는 방식