[Daily morning study] 메시지 큐(Kafka, RabbitMQ) 개념과 활용

#daily morning study

Image


메시지 큐(Kafka, RabbitMQ) 개념과 활용

메시지 큐란

메시지 큐(Message Queue)는 서비스 간 비동기 통신을 가능하게 해주는 미들웨어다. 생산자(Producer)가 메시지를 큐에 넣으면, 소비자(Consumer)가 나중에 꺼내서 처리한다. 두 서비스가 동시에 실행되고 있을 필요가 없기 때문에 시스템 간 결합도를 낮추는 데 핵심적인 역할을 한다.

왜 필요한가

  • 트래픽 급증 시 메시지를 큐에 쌓아두고 소비자가 여유롭게 처리 → 부하 분산
  • 소비자 서비스가 일시 중단돼도 메시지를 잃지 않음 → 내결함성
  • 생산자가 소비자의 응답을 기다리지 않음 → 응답 시간 단축
  • 여러 소비자가 독립적으로 메시지를 처리 → 확장성

핵심 개념

용어설명
Producer메시지를 생성해서 큐/토픽에 보내는 쪽
Consumer큐/토픽에서 메시지를 꺼내 처리하는 쪽
Queue / Topic메시지가 저장되는 공간
Broker메시지를 중개하는 서버 (Kafka, RabbitMQ 자체가 브로커)
Acknowledgment (ACK)소비자가 메시지를 정상 처리했다고 브로커에 알리는 신호

RabbitMQ

동작 방식

RabbitMQ는 AMQP(Advanced Message Queuing Protocol) 기반의 전통적인 메시지 브로커다. Producer가 메시지를 Exchange에 보내면, Exchange가 바인딩 규칙에 따라 하나 이상의 Queue로 라우팅한다. Consumer는 Queue에서 메시지를 꺼내 처리한다.

Producer → Exchange → (Binding) → Queue → Consumer

Exchange 타입

타입설명
Direct라우팅 키가 정확히 일치하는 Queue에 전달
Fanout연결된 모든 Queue에 브로드캐스트
Topic라우팅 키 패턴 매칭 (와일드카드 지원: *, #)
Headers헤더 속성 기반 라우팅

특징

  • 메시지를 처리하면 큐에서 삭제됨 (소비 후 사라짐)
  • ACK를 받아야 큐에서 제거 → ACK 전에 Consumer가 죽으면 재전송
  • 복잡한 라우팅 로직이 필요할 때 유리
  • 실시간 태스크 큐, 이메일 발송, 알림 처리 같은 단건 처리 패턴에 적합

간단한 Python 예시 (pika 라이브러리)

import pika

# Producer
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
channel.basic_publish(
    exchange='',
    routing_key='task_queue',
    body='Hello World',
    properties=pika.BasicProperties(delivery_mode=2)  # 메시지 영속성
)
connection.close()

# Consumer
def callback(ch, method, properties, body):
    print(f"Received: {body.decode()}")
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='task_queue', on_message_callback=callback)
channel.start_consuming()

Apache Kafka

동작 방식

Kafka는 분산 이벤트 스트리밍 플랫폼으로, 대용량 실시간 데이터 처리를 위해 설계됐다. 메시지는 Topic으로 분류되고, Topic은 여러 Partition으로 나뉜다. 각 Partition은 순서가 보장된 로그 파일이다.

Producer → Topic (Partition 0, 1, 2, ...) → Consumer Group

핵심 구조

개념설명
Topic메시지 스트림의 카테고리
PartitionTopic을 분할한 단위, 병렬 처리의 기본 단위
OffsetPartition 내 메시지의 순서 번호
Consumer Group여러 Consumer가 협력해 Topic을 분산 처리하는 논리적 그룹
BrokerKafka 서버 인스턴스, 클러스터로 구성
ZooKeeper / KRaft브로커 메타데이터 관리 (KRaft는 ZooKeeper 없이 동작)

Partition과 Consumer Group

  • 하나의 Partition은 같은 Consumer Group 내 하나의 Consumer만 처리
  • Consumer Group 수를 늘리면 같은 메시지를 여러 시스템이 독립적으로 소비 가능
  • Partition 수 = Consumer Group 내 최대 병렬 처리 단위
Topic (3 Partitions)
  P0 → Consumer A
  P1 → Consumer B
  P2 → Consumer C

RabbitMQ와의 핵심 차이: 메시지 보존

Kafka는 메시지를 소비해도 삭제하지 않는다. 설정한 보존 기간(기본 7일)이 지나거나 디스크 한도에 도달할 때 삭제된다. Consumer는 Offset을 직접 관리하므로, 필요하면 과거 메시지를 재처리할 수 있다.

특징

  • 초당 수백만 건의 메시지 처리 가능 → 고처리량
  • 메시지를 디스크에 순차 기록 → 내구성
  • 이벤트 소싱, 로그 수집, 실시간 분석 파이프라인에 적합
  • 설정과 운영 복잡도가 RabbitMQ보다 높음

간단한 Python 예시 (kafka-python 라이브러리)

from kafka import KafkaProducer, KafkaConsumer
import json

# Producer
producer = KafkaProducer(
    bootstrap_servers=['localhost:9092'],
    value_serializer=lambda v: json.dumps(v).encode('utf-8')
)
producer.send('user-events', {'user_id': 42, 'action': 'login'})
producer.flush()

# Consumer
consumer = KafkaConsumer(
    'user-events',
    bootstrap_servers=['localhost:9092'],
    group_id='analytics-group',
    value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)
for message in consumer:
    print(f"Offset {message.offset}: {message.value}")

RabbitMQ vs Kafka 비교

항목RabbitMQKafka
프로토콜AMQP자체 바이너리 프로토콜
메시지 보존소비 후 삭제설정 기간 보존
처리량중간 (수만 건/초)매우 높음 (수백만 건/초)
순서 보장Queue 단위Partition 단위
라우팅Exchange를 통한 유연한 라우팅Topic + Partition 기반
재처리기본 어려움 (DLQ 활용 가능)Offset 재설정으로 용이
적합한 용도태스크 큐, 복잡한 라우팅이벤트 스트리밍, 로그 파이프라인
운영 복잡도낮음높음

실무 선택 기준

RabbitMQ를 선택하는 경우

  • 이메일 발송, 결제 처리처럼 각 메시지를 정확히 한 번 처리해야 할 때
  • 복잡한 라우팅 규칙이 필요할 때
  • 팀이 빠르게 도입하고 싶을 때

Kafka를 선택하는 경우

  • 사용자 행동 로그, 클릭스트림처럼 대용량 이벤트를 실시간으로 처리해야 할 때
  • 여러 시스템이 같은 이벤트를 독립적으로 소비해야 할 때
  • 과거 데이터를 재처리하거나 이벤트 소싱 패턴을 구현할 때

메시지 전달 보장 수준

수준설명
At-most-once메시지가 유실될 수 있지만 중복 없음
At-least-once메시지가 반드시 전달되지만 중복 가능
Exactly-once정확히 한 번 전달 (구현 복잡, 성능 저하)

Kafka는 At-least-once가 기본이며, Idempotent Producer + Transactional API를 활용하면 Exactly-once도 가능하다. RabbitMQ는 ACK 기반으로 At-least-once를 보장한다.