🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

이벤트 기반 통신 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 21. · 2 Views

이벤트 기반 통신 완벽 가이드

RabbitMQ와 Kafka를 활용한 이벤트 기반 아키텍처의 핵심 개념부터 실무 선택 기준까지, 초급 개발자를 위한 쉽고 명확한 안내서입니다. Docker 실습 예제와 함께 메시지 브로커의 세계를 탐험해보세요.


목차

  1. 이벤트_기반_아키텍처
  2. RabbitMQ_소개
  3. Kafka_소개
  4. RabbitMQ_vs_Kafka
  5. 메시지_브로커_선택
  6. Docker로_실행

1. 이벤트 기반 아키텍처

입사 6개월 차 김개발 씨는 오늘도 회의실에서 난감한 상황에 처했습니다. "주문이 완료되면 재고 시스템, 배송 시스템, 알림 시스템이 모두 업데이트되어야 하는데...

이걸 어떻게 연결하죠?" 선배 개발자 박시니어 씨가 조용히 미소 지으며 말했습니다. "좋은 질문이에요.

바로 이벤트 기반 아키텍처가 필요한 시점이죠."

이벤트 기반 아키텍처는 시스템의 각 부분이 직접 통신하지 않고, 이벤트라는 메시지를 통해 간접적으로 소통하는 설계 방식입니다. 마치 신문사가 신문을 발행하면 구독자들이 각자 읽어보는 것처럼, 한 시스템이 이벤트를 발행하면 관심 있는 시스템들이 각자 처리합니다.

이를 통해 시스템 간 결합도를 낮추고 유연성을 높일 수 있습니다.

다음 코드를 살펴봅시다.

// 주문 서비스에서 이벤트 발행
@Service
public class OrderService {
    private final EventPublisher eventPublisher;

    public void createOrder(Order order) {
        // 주문 저장
        orderRepository.save(order);

        // 이벤트 발행 - 다른 시스템에 알림
        OrderCreatedEvent event = new OrderCreatedEvent(
            order.getId(),
            order.getProductId(),
            order.getQuantity()
        );
        eventPublisher.publish(event);
    }
}

김개발 씨는 지난주에 큰 문제를 겪었습니다. 주문 서비스를 수정했는데, 갑자기 재고 시스템과 배송 시스템이 모두 오류를 뿜어냈던 것입니다.

세 시스템이 서로 강하게 연결되어 있었기 때문입니다. 선배 박시니어 씨가 커피를 한 모금 마시며 설명을 시작했습니다.

"김 개발 씨, 지금 우리 시스템은 마치 전화 통화처럼 되어 있어요. 주문 서비스가 재고 시스템에 직접 전화를 걸고, 배송 시스템에도 직접 전화를 걸죠." 그렇다면 이벤트 기반 아키텍처란 정확히 무엇일까요?

쉽게 비유하자면, 이벤트 기반 아키텍처는 마치 유튜브 구독 시스템과 같습니다. 유튜버가 영상을 올리면 구독자들에게 알림이 갑니다.

유튜버는 누가 구독하는지 일일이 알 필요가 없고, 구독자들은 원하는 채널만 골라서 볼 수 있습니다. 이처럼 이벤트 기반 아키텍처도 발행자구독자를 분리하는 역할을 담당합니다.

이벤트 기반 아키텍처가 없던 시절에는 어땠을까요? 개발자들은 시스템 간 연결을 직접 코드로 작성해야 했습니다.

주문 서비스가 재고 시스템의 API를 직접 호출하고, 배송 시스템의 메서드를 직접 실행했습니다. 코드가 복잡해지고, 한 시스템의 변경이 다른 모든 시스템에 영향을 미쳤습니다.

더 큰 문제는 한 시스템이 다운되면 연결된 모든 시스템이 오류를 발생시켰다는 점입니다. 프로젝트가 커질수록 이런 문제는 눈덩이처럼 불어났습니다.

바로 이런 문제를 해결하기 위해 이벤트 기반 아키텍처가 등장했습니다. 이벤트 기반 아키텍처를 사용하면 시스템 간 독립성이 가능해집니다.

주문 서비스는 그저 "주문이 생성되었다"는 이벤트만 발행하면 됩니다. 누가 이 이벤트를 받아서 처리하는지 알 필요가 없습니다.

또한 확장성도 얻을 수 있습니다. 새로운 알림 시스템을 추가하고 싶다면, 기존 코드를 전혀 수정하지 않고 그냥 이벤트를 구독하기만 하면 됩니다.

무엇보다 장애 격리라는 큰 이점이 있습니다. 재고 시스템이 일시적으로 다운되어도, 이벤트는 메시지 큐에 안전하게 저장되어 있다가 시스템이 복구되면 처리됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 orderRepository.save(order) 부분을 보면 주문 데이터를 데이터베이스에 저장하는 것을 알 수 있습니다.

이 부분이 핵심 비즈니스 로직입니다. 다음으로 OrderCreatedEvent 객체를 생성하는 부분에서는 다른 시스템에 전달할 정보를 담습니다.

주문 ID, 상품 ID, 수량 등 필요한 데이터만 포함합니다. 마지막으로 eventPublisher.publish(event)를 호출하면 이벤트가 메시지 브로커로 전송되고, 구독 중인 모든 시스템에 전달됩니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 이커머스 플랫폼을 개발한다고 가정해봅시다.

사용자가 주문 버튼을 클릭하면, 주문 서비스는 OrderCreated 이벤트를 발행합니다. 재고 서비스는 이 이벤트를 받아서 재고를 차감하고, 배송 서비스는 배송 준비를 시작하고, 알림 서비스는 고객에게 주문 확인 메시지를 보냅니다.

쿠폰 서비스는 사용된 쿠폰을 처리하고, 포인트 서비스는 적립 포인트를 계산합니다. 이 모든 작업이 비동기적으로, 독립적으로 진행됩니다.

네이버, 쿠팡, 배달의민족 같은 대형 서비스에서 이런 패턴을 적극적으로 사용하고 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 모든 통신을 이벤트로 처리하려는 것입니다. 간단한 조회나 즉각적인 응답이 필요한 경우에는 REST API 같은 동기 방식이 더 적합합니다.

이벤트 기반 아키텍처는 비동기 처리가 가능하고 시스템 간 독립성이 중요한 경우에 사용해야 합니다. 따라서 상황에 맞는 적절한 통신 방식을 선택하는 것이 중요합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, 그래서 대형 서비스들이 마이크로서비스 아키텍처를 도입할 때 이벤트 기반 통신을 사용하는 거군요!" 이벤트 기반 아키텍처를 제대로 이해하면 더 유연하고 확장 가능한 시스템을 설계할 수 있습니다. 시스템 간 결합도를 낮추고, 장애에 강한 안정적인 서비스를 만들 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 모든 통신을 이벤트로 처리하지 말고, 동기/비동기 특성에 맞게 선택하세요

  • 이벤트 이름은 과거형으로 작성합니다 (OrderCreated, PaymentCompleted)
  • 이벤트에는 최소한의 필요한 데이터만 포함하세요

2. RabbitMQ 소개

팀 회의에서 이벤트 기반 아키텍처를 도입하기로 결정한 후, 김개발 씨는 또 다른 고민에 빠졌습니다. "이벤트를 어떻게 전달하죠?

그냥 HTTP로 보내면 안 되나요?" 박시니어 씨가 화이트보드에 토끼 그림을 그리며 웃었습니다. "RabbitMQ라는 친구를 소개해드릴게요."

RabbitMQ는 메시지를 안전하게 전달하는 메시지 브로커입니다. 발행자가 보낸 메시지를 에 저장했다가 구독자에게 전달하는 우체국 같은 역할을 합니다.

AMQP 프로토콜을 기반으로 동작하며, 메시지 라우팅, 보장된 전달, 우선순위 처리 등 다양한 기능을 제공합니다.

다음 코드를 살펴봅시다.

// RabbitMQ 설정
@Configuration
public class RabbitMQConfig {
    @Bean
    public Queue orderQueue() {
        // durable: 서버 재시작 시에도 큐 유지
        return new Queue("order.created", true);
    }

    @Bean
    public TopicExchange orderExchange() {
        return new TopicExchange("order.exchange");
    }

    @Bean
    public Binding binding(Queue queue, TopicExchange exchange) {
        // 라우팅 키로 메시지 필터링
        return BindingBuilder.bind(queue)
            .to(exchange)
            .with("order.created.*");
    }
}

김개발 씨는 처음에 단순하게 생각했습니다. "주문 서비스에서 재고 서비스 API를 HTTP로 호출하면 되지 않나요?" 하지만 실제로 구현해보니 문제가 한두 가지가 아니었습니다.

재고 서비스가 잠깐 점검 중일 때 주문이 실패했고, 트래픽이 몰릴 때는 타임아웃이 발생했습니다. 박시니어 씨가 차분하게 설명을 시작했습니다.

"직접 API를 호출하는 건 마치 상대방에게 직접 전화를 거는 거예요. 상대가 전화를 안 받으면?

통화 중이면? 그냥 실패하죠." 그렇다면 RabbitMQ란 정확히 무엇일까요?

쉽게 비유하자면, RabbitMQ는 마치 우체국과 같습니다. 편지를 보낼 때 받는 사람이 집에 없어도 우체통에 넣어두면 됩니다.

우체국이 안전하게 보관했다가 나중에 전달해줍니다. 받는 사람이 여행 중이어도 괜찮습니다.

돌아오면 편지를 받을 수 있으니까요. 이처럼 RabbitMQ도 메시지를 안전하게 보관하고 적절한 시점에 전달하는 역할을 담당합니다.

RabbitMQ가 없던 시절에는 어땠을까요? 개발자들은 메시지 전달 실패를 직접 처리해야 했습니다.

재시도 로직을 코드에 넣고, 실패한 메시지를 별도로 저장하고, 나중에 다시 보내는 배치 작업을 만들었습니다. 코드가 복잡해지고, 실수하기도 쉬웠습니다.

더 큰 문제는 메시지 순서가 보장되지 않는다는 점이었습니다. 첫 번째 메시지가 실패하고 두 번째가 성공하면, 데이터 정합성이 깨졌습니다.

프로젝트가 커질수록 이런 문제는 관리하기 어려워졌습니다. 바로 이런 문제를 해결하기 위해 RabbitMQ 같은 메시지 브로커가 등장했습니다.

RabbitMQ를 사용하면 메시지 보장 전달이 가능해집니다. 메시지를 큐에 안전하게 저장하고, 구독자가 처리를 완료했다는 확인을 받을 때까지 보관합니다.

또한 복잡한 라우팅도 얻을 수 있습니다. Exchange와 Binding을 통해 메시지를 원하는 큐로 정확하게 전달할 수 있습니다.

무엇보다 부하 분산이라는 큰 이점이 있습니다. 여러 구독자가 하나의 큐를 공유하면, 메시지가 자동으로 분산 처리됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 Queue("order.created", true) 부분을 보면 "order.created"라는 이름의 큐를 생성하는 것을 알 수 있습니다.

두 번째 파라미터 truedurable 설정으로, RabbitMQ 서버가 재시작되어도 큐가 사라지지 않도록 보장합니다. 이 부분이 핵심입니다.

다음으로 TopicExchange는 라우팅 키 패턴을 사용해서 메시지를 필터링하는 교환기입니다. 마지막으로 BindingBuilder를 통해 교환기와 큐를 연결하고, "order.created.*" 패턴과 일치하는 메시지만 이 큐로 전달되도록 설정합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 쇼핑몰 주문 시스템을 개발한다고 가정해봅시다.

주문이 생성되면 여러 작업이 필요합니다. 재고 차감, 결제 처리, 배송 준비, 고객 알림, 포인트 적립 등입니다.

이 모든 작업을 RabbitMQ 큐를 통해 처리하면, 각 시스템이 독립적으로 동작하면서도 안정적으로 메시지를 받을 수 있습니다. 재고 시스템이 일시적으로 다운되어도 메시지는 큐에 쌓여있다가 시스템이 복구되면 처리됩니다.

토스, 당근마켓 같은 국내 스타트업들도 RabbitMQ를 적극 활용하고 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 메시지 처리 후 ACK를 제대로 보내지 않는 것입니다. RabbitMQ는 구독자가 메시지를 성공적으로 처리했다는 확인(ACK)을 받아야 큐에서 메시지를 삭제합니다.

ACK를 보내지 않으면 메시지가 큐에 계속 남아서 메모리 문제가 발생할 수 있습니다. 따라서 메시지 처리가 완료된 후 반드시 ACK를 전송하도록 구현해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, 그래서 RabbitMQ가 안정적인 메시징 시스템으로 유명한 거군요!" RabbitMQ를 제대로 이해하면 더 안정적이고 확장 가능한 시스템을 구축할 수 있습니다. 메시지 유실 걱정 없이 비동기 처리를 할 수 있고, 시스템 간 결합도를 낮출 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 메시지는 반드시 직렬화 가능한 형태로 전송하세요 (JSON, Protobuf 등)

  • durable 큐와 persistent 메시지를 함께 사용해야 완전한 내구성을 보장합니다
  • Dead Letter Queue를 설정해서 처리 실패한 메시지를 따로 관리하세요

3. Kafka 소개

RabbitMQ를 도입한 지 몇 달 후, 김개발 씨의 팀은 새로운 과제를 받았습니다. "하루 1억 건의 로그 데이터를 실시간으로 처리해야 합니다." 박시니어 씨가 노트북을 열며 말했습니다.

"이제 Kafka를 배울 때가 됐네요. RabbitMQ로는 이 정도 규모는 힘들어요."

Kafka는 LinkedIn에서 개발한 분산 스트리밍 플랫폼입니다. 초당 수백만 건의 메시지를 처리할 수 있으며, 로그 형태로 메시지를 저장하여 나중에 다시 읽을 수 있습니다.

RabbitMQ와 달리 메시지를 소비해도 삭제되지 않고, 설정된 기간 동안 보관됩니다. 대용량 데이터 스트리밍과 이벤트 소싱에 최적화되어 있습니다.

다음 코드를 살펴봅시다.

// Kafka Producer 설정
@Configuration
public class KafkaProducerConfig {
    @Bean
    public ProducerFactory<String, OrderEvent> producerFactory() {
        Map<String, Object> config = new HashMap<>();
        config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        // 메시지 직렬화 설정
        config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        // 안정성을 위한 acks 설정 (모든 복제본이 확인)
        config.put(ProducerConfig.ACKS_CONFIG, "all");
        return new DefaultKafkaProducerFactory<>(config);
    }

    @Bean
    public KafkaTemplate<String, OrderEvent> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

김개발 씨는 RabbitMQ로 잘 작동하는 시스템을 보며 뿌듯해했습니다. 하루 10만 건 정도의 주문을 안정적으로 처리하고 있었으니까요.

그런데 새로운 요구사항은 차원이 달랐습니다. 사용자 행동 로그, 클릭 이벤트, 검색 기록 등을 실시간으로 수집하고 분석해야 한다는 것입니다.

박시니어 씨가 웃으며 말했습니다. "RabbitMQ는 훌륭한 우체국이에요.

하지만 하루에 화물차 수천 대 분량의 물건을 처리하기에는 적합하지 않죠. 이럴 때는 물류 센터가 필요해요." 그렇다면 Kafka란 정확히 무엇일까요?

쉽게 비유하자면, Kafka는 마치 대형 물류 센터와 같습니다. 쿠팡이나 아마존의 물류 창고를 생각해보세요.

수많은 물건이 들어오고, 정리되고, 필요한 곳으로 배송됩니다. 중요한 점은 물건의 기록이 남는다는 것입니다.

나중에 "지난주 화요일에 배송된 물건 목록"을 다시 확인할 수 있습니다. 이처럼 Kafka도 대량의 메시지를 처리하면서 기록을 보관하는 역할을 담당합니다.

Kafka가 없던 시절에는 어땠을까요? 대용량 로그 데이터를 처리하기 위해 개발자들은 파일 시스템에 로그를 저장하고, 배치 작업으로 나중에 처리했습니다.

실시간 분석은 어려웠고, 데이터가 늘어나면 디스크가 금방 찼습니다. 더 큰 문제는 같은 데이터를 여러 시스템에서 사용해야 할 때였습니다.

로그를 파일로 복사해서 전달하거나, 각 시스템마다 별도로 수집해야 했습니다. 프로젝트가 커질수록 데이터 파이프라인 관리가 악몽이 되었습니다.

바로 이런 문제를 해결하기 위해 Kafka가 탄생했습니다. Kafka를 사용하면 초고속 메시지 처리가 가능해집니다.

파티션이라는 개념으로 메시지를 분산 저장하여, 여러 서버가 병렬로 처리합니다. 또한 메시지 재사용도 얻을 수 있습니다.

RabbitMQ는 메시지를 소비하면 삭제되지만, Kafka는 설정된 기간 동안 보관하므로 같은 데이터를 여러 번 읽을 수 있습니다. 무엇보다 확장성이라는 큰 이점이 있습니다.

서버를 추가하는 것만으로 처리량을 쉽게 늘릴 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 BOOTSTRAP_SERVERS_CONFIG를 보면 Kafka 서버의 주소를 지정하는 것을 알 수 있습니다. 여러 서버를 콤마로 구분해서 나열할 수 있습니다.

이 부분이 분산 시스템의 시작점입니다. 다음으로 ACKS_CONFIG를 "all"로 설정하면, 메시지가 모든 복제본에 저장되었음을 확인한 후에야 성공으로 처리됩니다.

이것이 데이터 안정성을 보장하는 핵심입니다. 마지막으로 JsonSerializer를 사용해서 객체를 JSON 형태로 직렬화하여 전송합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 넷플릭스 같은 스트리밍 서비스를 개발한다고 가정해봅시다.

사용자가 영상을 재생하면 수많은 이벤트가 발생합니다. 재생 시작, 일시정지, 탐색, 종료, 화질 변경 등입니다.

이 모든 이벤트를 Kafka 토픽으로 전송하면, 추천 시스템은 이 데이터로 개인화 추천을 개선하고, 분석 시스템은 인기 콘텐츠를 파악하고, 과금 시스템은 시청 시간을 계산합니다. 같은 이벤트 스트림을 여러 시스템이 각자의 목적으로 활용하는 것입니다.

실제로 넷플릭스, 우버, 에어비앤비 같은 글로벌 기업들이 Kafka를 핵심 인프라로 사용하고 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 파티션 키를 제대로 설정하지 않는 것입니다. Kafka는 파티션 키를 기준으로 메시지를 분산합니다.

키를 null로 보내면 라운드 로빈 방식으로 분산되어, 같은 사용자의 이벤트가 순서 없이 흩어질 수 있습니다. 순서가 중요한 경우에는 사용자 ID나 주문 ID를 파티션 키로 사용해야 같은 키의 메시지가 같은 파티션에 순서대로 저장됩니다.

따라서 데이터의 특성에 맞게 파티션 전략을 신중하게 선택해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 눈이 반짝였습니다. "아, 그래서 빅데이터 처리 파이프라인에 Kafka가 필수적으로 들어가는 거군요!" Kafka를 제대로 이해하면 대규모 실시간 데이터 처리 시스템을 구축할 수 있습니다.

수백만 사용자의 이벤트를 실시간으로 수집하고, 여러 시스템이 동시에 활용할 수 있는 강력한 데이터 파이프라인을 만들 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 토픽의 파티션 개수는 나중에 늘릴 수 있지만 줄일 수는 없으니 신중하게 결정하세요

  • Consumer Group을 활용하면 여러 인스턴스가 메시지를 분산 처리할 수 있습니다
  • Retention 정책을 적절히 설정해서 디스크 공간을 효율적으로 관리하세요

4. RabbitMQ vs Kafka

새로운 프로젝트 킥오프 미팅에서 아키텍처 논의가 한창입니다. 한 동료가 물었습니다.

"메시지 브로커는 뭘 쓸까요? RabbitMQ요, Kafka요?" 김개발 씨도 궁금했습니다.

둘 다 좋아 보이는데, 어떻게 선택해야 할까요? 박시니어 씨가 화이트보드에 표를 그리며 차이점을 설명하기 시작했습니다.

RabbitMQKafka는 모두 메시지 브로커지만 설계 철학이 다릅니다. RabbitMQ는 메시지 전달과 라우팅에 강점이 있고, Kafka는 대용량 스트리밍과 로그 저장에 최적화되어 있습니다.

RabbitMQ는 메시지를 소비하면 삭제하지만, Kafka는 보관 기간 동안 유지합니다. 사용 목적과 데이터 특성에 따라 선택해야 합니다.

다음 코드를 살펴봅시다.

// RabbitMQ: 작업 큐 패턴 (메시지 소비 후 삭제)
@RabbitListener(queues = "task.queue")
public void handleTask(TaskMessage task) {
    // 작업 처리
    processTask(task);
    // 처리 완료되면 메시지는 큐에서 삭제됨
}

// Kafka: 이벤트 스트림 패턴 (메시지 보관)
@KafkaListener(topics = "user.events", groupId = "analytics-service")
public void handleEvent(UserEvent event) {
    // 이벤트 분석
    analyzeUserBehavior(event);
    // 메시지는 Kafka에 남아있어서 다른 컨슈머도 읽을 수 있음
}

김개발 씨는 지난 며칠간 고민이 많았습니다. 새 프로젝트에서 어떤 메시지 브로커를 선택해야 할지 결정해야 했기 때문입니다.

인터넷을 검색하면 "RabbitMQ vs Kafka" 비교 글이 수도 없이 나왔지만, 구체적으로 어떤 상황에 무엇을 써야 하는지는 여전히 헷갈렸습니다. 박시니어 씨가 차분하게 설명을 시작했습니다.

"둘을 비교하기 전에, 각자가 태어난 이유부터 알아야 해요. RabbitMQ는 '메시지를 어떻게 안전하게 전달할까'에서 시작했고, Kafka는 'LinkedIn의 엄청난 로그를 어떻게 처리할까'에서 시작했어요." 그렇다면 이 둘의 차이는 정확히 무엇일까요?

쉽게 비유하자면, RabbitMQ는 택배 서비스이고 Kafka는 신문 구독 서비스입니다. 택배는 물건을 정확한 주소로 배달하고, 받는 사람이 받으면 배달이 완료됩니다.

다시 배달할 필요가 없습니다. 반면 신문은 매일 발행되고, 여러 구독자가 같은 신문을 읽습니다.

지난주 신문을 다시 읽고 싶으면 보관함에서 꺼내볼 수도 있습니다. 이처럼 RabbitMQ는 일회성 메시지 전달에, Kafka는 지속적인 스트림 처리에 적합합니다.

두 시스템을 잘못 선택하면 어떤 문제가 생길까요? RabbitMQ로 하루 1억 건의 로그를 처리하려고 하면 성능 병목이 발생합니다.

RabbitMQ는 각 메시지를 정확하게 라우팅하고 ACK를 받는 과정이 있어서, 초고속 대량 처리에는 오버헤드가 큽니다. 반대로 Kafka로 간단한 작업 큐를 구현하면 과도하게 복잡해집니다.

10분마다 몇 건의 이메일을 보내는데 Kafka를 띄우는 건 "칼로 두부 자르기"입니다. 더 큰 문제는 유지보수 비용입니다.

필요하지 않은 기능을 위해 복잡한 시스템을 운영하면, 팀의 리소스가 낭비됩니다. 그렇다면 언제 무엇을 선택해야 할까요?

RabbitMQ를 선택해야 하는 경우는 다음과 같습니다. 메시지 처리 후 삭제해도 되는 작업 큐가 필요할 때, 복잡한 라우팅 규칙이 필요할 때, 처리량이 초당 수만 건 이하일 때입니다.

예를 들어 주문 처리, 이메일 발송, 이미지 리사이징 같은 작업 분산에 적합합니다. Kafka를 선택해야 하는 경우는 이렇습니다.

같은 데이터를 여러 시스템에서 사용해야 할 때, 과거 데이터를 다시 읽어야 할 때, 처리량이 초당 수십만 건 이상일 때입니다. 예를 들어 사용자 행동 로그, 실시간 분석, 이벤트 소싱 같은 데이터 스트리밍에 최적입니다.

위의 코드를 비교해 보겠습니다. RabbitMQ 리스너를 보면 handleTask 메서드가 작업을 처리하고 나면 메시지는 자동으로 큐에서 제거됩니다.

한 번 처리하면 끝입니다. 반면 Kafka 리스너는 groupId라는 개념이 있습니다.

같은 토픽을 여러 Consumer Group이 구독할 수 있고, 각 그룹은 독립적으로 메시지를 읽습니다. "analytics-service" 그룹이 읽어도 메시지는 그대로 남아있어서, "recommendation-service" 그룹도 같은 메시지를 읽을 수 있습니다.

실제 현업에서는 어떻게 선택할까요? 토스를 예로 들어봅시다.

송금 요청을 처리하는 시스템은 RabbitMQ를 쓸 가능성이 높습니다. 한 번 처리하면 끝이고, 정확한 전달과 순서 보장이 중요하기 때문입니다.

반면 사용자가 앱에서 어떤 화면을 보고, 어떤 버튼을 클릭했는지 수집하는 로그 시스템은 Kafka를 쓸 것입니다. 수백만 사용자의 이벤트를 실시간으로 모아서, 분석팀도 보고, 추천팀도 보고, 마케팅팀도 봐야 하기 때문입니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 트렌드만 보고 선택하는 것입니다.

"요즘 다들 Kafka 쓴다던데?"라며 무조건 Kafka를 도입하면, 간단한 작업 큐를 위해 복잡한 클러스터를 운영하게 됩니다. 반대로 "RabbitMQ가 설정이 쉽다던데?"라며 대용량 로그 처리에 RabbitMQ를 쓰면 성능 문제에 직면합니다.

따라서 실제 요구사항과 데이터 특성을 먼저 분석하고 선택해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 우리 프로젝트는 주문 처리가 주목적이니까 RabbitMQ가 더 적합하겠네요!" 두 시스템의 차이를 제대로 이해하면 프로젝트에 맞는 올바른 선택을 할 수 있습니다.

때로는 둘 다 사용하는 것도 좋은 전략입니다. 작업 큐는 RabbitMQ로, 로그 수집은 Kafka로 처리하는 것처럼 말이죠.

여러분도 오늘 배운 내용을 바탕으로 현명한 선택을 해보세요.

실전 팁

💡 - 둘 중 하나를 선택할 때는 "메시지를 다시 읽어야 하는가?"를 먼저 물어보세요

  • 처리량이 애매하면 RabbitMQ로 시작해서 필요시 Kafka로 마이그레이션하는 것도 방법입니다
  • 두 시스템을 함께 사용하는 하이브리드 아키텍처도 고려해보세요

5. 메시지 브로커 선택

프로젝트 요구사항 문서를 읽던 김개발 씨는 머리가 복잡해졌습니다. "하루 50만 건의 주문, 실시간 알림, 데이터 분석까지...

이걸 어떻게 설계하죠?" 박시니어 씨가 체크리스트를 꺼내며 웃었습니다. "선택 기준을 하나씩 체크해봅시다.

그러면 답이 보일 거예요."

메시지 브로커를 선택할 때는 처리량, 메시지 보관 필요성, 순서 보장, 구독 패턴을 종합적으로 고려해야 합니다. 단순 작업 분산은 RabbitMQ가, 대용량 스트리밍은 Kafka가 적합합니다.

데이터 재사용 필요 여부가 가장 중요한 판단 기준이며, 운영 복잡도와 팀의 기술 역량도 함께 고려해야 합니다.

다음 코드를 살펴봅시다.

// 선택 기준을 코드로 표현
public class MessageBrokerSelector {
    public BrokerType selectBroker(ProjectRequirements req) {
        // 1. 처리량 확인
        if (req.getMessagesPerSecond() > 100000) {
            return BrokerType.KAFKA; // 초당 10만 건 이상은 Kafka
        }

        // 2. 메시지 재사용 필요성
        if (req.needsMultipleConsumers() || req.needsReplay()) {
            return BrokerType.KAFKA; // 여러 구독자나 재처리 필요시 Kafka
        }

        // 3. 복잡한 라우팅 필요
        if (req.hasComplexRouting()) {
            return BrokerType.RABBITMQ; // 정교한 라우팅은 RabbitMQ
        }

        // 4. 기본 선택
        return BrokerType.RABBITMQ; // 간단한 작업 큐는 RabbitMQ
    }
}

김개발 씨는 지난 회의에서 두 가지 의견이 충돌하는 걸 봤습니다. 백엔드 팀장은 "Kafka로 가야 한다"고 했고, 데브옵스 팀장은 "RabbitMQ면 충분하다"고 했습니다.

둘 다 경험 많은 개발자들인데, 왜 의견이 다를까요? 박시니어 씨가 조용히 설명했습니다.

"둘 다 맞을 수도, 틀릴 수도 있어요. 중요한 건 우리 프로젝트의 요구사항을 정확히 파악하는 거예요.

기술 선택은 그 다음입니다." 그렇다면 정확히 어떤 기준으로 선택해야 할까요? 쉽게 비유하자면, 메시지 브로커를 선택하는 건 마치 교통수단을 선택하는 것과 같습니다.

서울에서 부산을 가는데, 혼자라면 KTX가 편하지만 가족 여행이라면 자동차가 좋습니다. 화물을 운송한다면 트럭이 필요하고, 급한 서류는 택배로 보냅니다.

목적지, 사람 수, 짐의 양, 시간, 비용을 모두 고려해야 합니다. 이처럼 메시지 브로커도 여러 요소를 종합적으로 판단해야 합니다.

잘못 선택하면 어떤 문제가 생길까요? 소규모 스타트업이 처음부터 Kafka 클러스터를 구축하면 어떻게 될까요?

하루 몇천 건의 메시지를 처리하는데, 주키퍼 3대, Kafka 브로커 3대를 운영하게 됩니다. 서버 비용도 비용이지만, 더 큰 문제는 운영 부담입니다.

장애 발생 시 대응할 전문 인력이 필요합니다. 반대로 대규모 서비스에서 RabbitMQ로 버티려고 하면 성능 병목에 직면합니다.

서버를 아무리 늘려도 처리량이 따라가지 못합니다. 그렇다면 체계적인 선택 기준은 무엇일까요?

첫 번째 기준은 처리량입니다. 초당 수천 건 수준이면 RabbitMQ로 충분합니다.

수만 건을 넘어가면 Kafka를 고려해야 하고, 수십만 건 이상이면 Kafka가 거의 필수입니다. 두 번째 기준은 메시지 재사용입니다.

한 번 처리하고 끝이면 RabbitMQ가 적합합니다. 같은 데이터를 여러 시스템에서 사용하거나, 과거 데이터를 다시 처리해야 한다면 Kafka가 답입니다.

세 번째 기준은 라우팅 복잡도입니다. "특정 조건에 맞는 메시지만 특정 큐로"같은 정교한 라우팅이 필요하면 RabbitMQ의 Exchange 시스템이 강력합니다.

Kafka는 토픽 기반의 단순한 구조입니다. 네 번째 기준은 운영 역량입니다.

팀에 Kafka 운영 경험이 없다면, 당장 필요하지 않은 상황에서 도입하는 건 위험합니다. RabbitMQ는 상대적으로 설정과 운영이 간단합니다.

위의 코드를 살펴보겠습니다. 이 코드는 선택 로직을 단순화한 것입니다.

실제로는 더 많은 요소를 고려하지만, 핵심 판단 기준을 보여줍니다. 먼저 초당 메시지 수를 확인합니다.

10만 건이 넘으면 Kafka의 영역입니다. 다음으로 여러 컨슈머가 같은 메시지를 읽어야 하는지 확인합니다.

이것이 Kafka의 핵심 강점이기 때문입니다. 복잡한 라우팅이 필요하면 RabbitMQ를 선택합니다.

그 외의 일반적인 작업 큐 용도라면 RabbitMQ가 무난합니다. 실제 현업에서는 어떻게 선택할까요?

배달의민족 같은 서비스를 생각해봅시다. 주문 처리 시스템은 RabbitMQ를 쓸 가능성이 높습니다.

주문이 들어오면 음식점에 알림을 보내고, 라이더를 배정하고, 결제를 처리합니다. 각 작업은 한 번씩만 처리되면 됩니다.

반면 사용자 행동 분석 시스템은 Kafka를 쓸 것입니다. 사용자가 어떤 메뉴를 검색했는지, 얼마나 고민했는지, 어떤 리뷰를 읽었는지 수집합니다.

이 데이터는 추천 알고리즘팀, 마케팅팀, 데이터 분석팀이 모두 사용합니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 하나만 선택해야 한다고 생각하는 것입니다. 실제로는 두 시스템을 함께 사용하는 경우가 많습니다.

배달의민족 예시처럼, 트랜잭션 처리는 RabbitMQ로, 로그 수집은 Kafka로 나누는 것입니다. 각각의 강점을 살리는 하이브리드 아키텍처가 현실적인 해법일 수 있습니다.

따라서 "둘 중 하나"가 아니라 "어디에 무엇을"이라는 관점으로 접근해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 체크리스트를 따라가던 김개발 씨는 고개를 끄덕였습니다. "아, 주문 처리는 RabbitMQ로, 사용자 로그는 Kafka로 분리하면 되겠네요!" 메시지 브로커 선택 기준을 제대로 이해하면 프로젝트에 맞는 최적의 아키텍처를 설계할 수 있습니다.

처리량, 재사용성, 라우팅, 운영 역량을 종합적으로 고려하고, 필요하다면 두 시스템을 함께 사용하는 것도 좋은 선택입니다. 여러분도 오늘 배운 기준을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 처음에는 보수적으로 선택하고, 성능 모니터링 후 필요시 전환하세요

  • 처리량을 측정할 때는 평균이 아닌 피크 시간대 기준으로 계산하세요
  • 운영 부담을 과소평가하지 마세요 - 단순한 것이 좋은 것입니다

6. Docker로 실행

이론은 충분히 배웠습니다. 이제 김개발 씨는 직접 실습해보고 싶었습니다.

"로컬에서 RabbitMQ랑 Kafka를 띄워서 테스트해볼 수 있을까요?" 박시니어 씨가 노트북을 열며 말했습니다. "Docker Compose 파일 하나면 5분 안에 둘 다 실행할 수 있어요."

Docker를 사용하면 RabbitMQ와 Kafka를 로컬 환경에 쉽게 구축할 수 있습니다. Docker Compose로 여러 컨테이너를 한 번에 실행하고, 설정을 코드로 관리할 수 있습니다.

개발 환경과 프로덕션 환경을 동일하게 유지할 수 있어, 환경 차이로 인한 문제를 예방할 수 있습니다.

다음 코드를 살펴봅시다.

version: '3.8'

services:
  # RabbitMQ 서비스
  rabbitmq:
    image: rabbitmq:3-management
    container_name: my-rabbitmq
    ports:
      - "5672:5672"   # AMQP 프로토콜 포트
      - "15672:15672" # 관리 UI 포트
    environment:
      RABBITMQ_DEFAULT_USER: admin
      RABBITMQ_DEFAULT_PASS: admin123

  # Kafka와 Zookeeper
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181

  kafka:
    image: confluentinc/cp-kafka:latest
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

김개발 씨는 예전에 RabbitMQ를 설치하려다 고생한 기억이 있습니다. Erlang을 먼저 설치하고, 환경 변수를 설정하고, 플러그인을 활성화하고...

한참 걸렸던 것 같습니다. Kafka는 더 복잡했습니다.

Zookeeper부터 설치해야 하고, 설정 파일도 여러 개였습니다. 박시니어 씨가 웃으며 말했습니다.

"이제는 그럴 필요가 없어요. Docker가 모든 걸 컨테이너로 패키징해놨거든요.

설치 과정을 건너뛰고 바로 사용할 수 있어요." 그렇다면 Docker로 실행하는 방법은 정확히 무엇일까요? 쉽게 비유하자면, Docker는 마치 즉석 캠핑카와 같습니다.

캠핑을 가려면 텐트, 침낭, 조리도구, 랜턴 등을 일일이 챙겨야 합니다. 하지만 캠핑카는 이 모든 게 갖춰져 있습니다.

그냥 시동 걸고 출발하면 됩니다. Docker 이미지도 마찬가지입니다.

RabbitMQ나 Kafka를 실행하는 데 필요한 모든 것이 이미 준비되어 있어서, 명령어 한 줄이면 실행됩니다. 이처럼 Docker는 환경 구축의 복잡함을 감춰주는 역할을 담당합니다.

Docker가 없던 시절에는 어땠을까요? 개발자들은 "내 컴퓨터에서는 잘 되는데요"라는 말을 자주 했습니다.

로컬 환경, 개발 서버, 운영 서버의 OS 버전이 다르고, 설치된 라이브러리가 달랐기 때문입니다. RabbitMQ를 Ubuntu에 설치하는 방법과 CentOS에 설치하는 방법이 달랐습니다.

더 큰 문제는 버전 관리였습니다. 한 프로젝트는 RabbitMQ 3.8이 필요하고, 다른 프로젝트는 3.9가 필요하면, 한 서버에 두 버전을 동시에 띄우기가 까다로웠습니다.

프로젝트가 늘어날수록 환경 관리가 악몽이 되었습니다. 바로 이런 문제를 해결하기 위해 Docker 같은 컨테이너 기술이 등장했습니다.

Docker를 사용하면 환경 일관성이 가능해집니다. 개발자 로컬, 테스트 서버, 운영 서버 모두 동일한 Docker 이미지를 사용하므로 환경 차이로 인한 문제가 사라집니다.

또한 빠른 실행도 얻을 수 있습니다. 가상 머신처럼 OS를 통째로 부팅하지 않고, 필요한 프로세스만 격리해서 실행하므로 몇 초 안에 시작됩니다.

무엇보다 간편한 제거라는 큰 이점이 있습니다. 테스트가 끝나면 docker-compose down 명령어 하나로 모든 컨테이너를 깔끔하게 정리할 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 RabbitMQ 부분을 보면 rabbitmq:3-management 이미지를 사용합니다.

이것은 RabbitMQ 3.x 버전에 관리 UI가 포함된 이미지입니다. 포트 매핑을 보면 5672는 실제 메시지 송수신에 사용하고, 15672는 웹 브라우저로 접속하는 관리 콘솔입니다.

다음으로 Kafka 부분을 보면 Zookeeper가 먼저 정의되어 있습니다. Kafka는 클러스터 정보를 Zookeeper에 저장하므로 의존성이 있습니다.

depends_on으로 이를 명시합니다. KAFKA_ADVERTISED_LISTENERS는 클라이언트가 Kafka에 접속할 주소를 설정하는 부분으로, 로컬 테스트이므로 localhost:9092로 지정합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 신규 팀원이 합류했다고 가정해봅시다.

예전에는 개발 환경 세팅 문서를 주고, "1번부터 20번까지 따라하세요"라고 했습니다. 하루 종일 걸리고, 중간에 오류가 나면 해결하느라 시간을 허비했습니다.

하지만 Docker Compose를 사용하면 git clone으로 프로젝트를 받고, docker-compose up -d 명령어 하나면 끝입니다. RabbitMQ, Kafka, PostgreSQL, Redis 등 필요한 모든 서비스가 자동으로 실행됩니다.

신규 팀원은 30분 안에 개발을 시작할 수 있습니다. 카카오, 라인, 당근마켓 같은 많은 기업이 이런 방식으로 개발 환경을 표준화하고 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 Docker 컨테이너를 프로덕션에 그대로 띄우는 것입니다.

위의 설정은 로컬 개발용입니다. 프로덕션에서는 데이터 영속성, 보안, 리소스 제한, 로깅 등 훨씬 많은 것을 고려해야 합니다.

예를 들어 RABBITMQ_DEFAULT_PASSadmin123 같은 간단한 비밀번호로 두면 보안 위험이 큽니다. 따라서 개발 환경과 프로덕션 환경의 설정을 분리하고, 프로덕션에서는 Kubernetes 같은 오케스트레이션 도구를 사용하는 것이 좋습니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨가 docker-compose up -d를 실행하자, 터미널에 로그가 주르륵 흘러갔습니다.

1분도 안 돼서 모든 서비스가 실행되었습니다. 김개발 씨는 감탄하며 말했습니다.

"와, 이렇게 간단하게 테스트 환경을 만들 수 있다니!" Docker를 제대로 활용하면 개발 생산성이 크게 향상됩니다. 환경 구축 시간을 절약하고, 팀원 간 환경 차이로 인한 문제를 예방할 수 있습니다.

또한 새로운 기술을 부담 없이 시도해볼 수 있습니다. 여러분도 오늘 배운 Docker Compose 파일을 저장해두고, 필요할 때마다 꺼내 쓰세요.

실전 팁

💡 - docker-compose.yml 파일은 Git에 커밋해서 팀원들과 공유하세요

  • 데이터를 유지하려면 volume을 설정해야 컨테이너를 삭제해도 데이터가 남습니다
  • 로컬 테스트 후에는 docker-compose down으로 리소스를 정리하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Spring Cloud#Event-Driven#RabbitMQ#Kafka#MessageBroker

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스