DevOps/devops

Kafka vs RabbitMQ & MSA환경에 kafka를 적용한다면? (with 예제코드)

bandal-gom 2024. 10. 26. 21:10

대규모 시스템이란

  • 방대한 양의 데이터를 처리하고 수 많은 사용자의 요청을 동시에 처리할 수 있는 시스템
  • 이러한 시스템 설계는 확장성, 유지보수성, 성능, 안정성 등을 고려
  • MSA 는 대규모 시스템 설계 아키텍쳐 패턴
  • RabbitMQ,Kafka 같은 메시징 시스템을 활용하여 각 서비스 간의 효율적인 데이터 통신과 확장성을 보장

메세징 시스템?

  • Queue 형태로 메세지를 저장. 송신자 (Producer)가 메세지를 큐에 넣으면, 수신자(Consumer)는 자신의 속도에 맞춰 Queue에서 메세지를 처리.
  • Message Broker를 사이에 두고, 송신자(Producer)와 수신자(Consumer)가 간접적으로 데이터를 주고 받음
  • MSA 환경에 적용한다면
    • 송신자 (Producer): MSA에서 메세지를 발행하는 곳 (e.g. 주문상태 변경에 대한 메세지를 order-msa에서 발행)
    • 수신자 (Consumer): MSA에서 메세지를 처리하는 곳 (e.g. 재고차감을 하기 위해 stock-msa에서 주문상태 변경 메세지를 수신)

기능

비동기 처리

Producer 메세지를 발행한 후, consumer가 메세지를 처리할 때까지 기다리지 않아도 됨 → 시스템의 유연성, 처리 효율성 ⬆️

 

데이터 손실 방지

데이터를 메세지 큐에 안전하게 보관. consumer가 메세지를 놓치지 않고 처리 가능 → 데이터 손실 방지, 신뢰성 있는 통신 보장

 

부하 분산

여러 consumer가 큐의 메세지를 가져가서 처리 → 시스템의 부하를 분산. 성능 ⬆️ 대용량 데이터를 처리할 때 유용

 

스케일링

메세지 큐 시스템은 수평적 확장이 용이. 더 많은 Producer, Consumer를 추가 가능 → 처리 능력 ⬆️

RabbitMQ vs Kafka

둘다 메세징 시스템이지만 어떤 점이 다를까?

RabbitMQ 구성도 (좌) Kafka 구성도 (우)

 

차이점 RabbitMQ Kafka
Producer ↔ Consumer의 상호작용 방식 Producer는 메세지를 보내고 메세지가 의도한 Consumer에 도착했는지 모니터링 → 우편물을 받아서, 수취인에게 배달하는 우체국 Producer는 Consumer가 메세지를 검색했는지에 대한 여부와 상관없이 Queue에 저장 → 다양한 장르의 메세지를 책장에 배치하는 도서관. 도서관 회원 (Consumer)가 책의 내용을 기억
아키텍쳐 - 복잡한 메시지 라우팅을 위해 설계
- 메세지큐 기반 설계
- push 모델 사용
- 더 복잡한 아키텍처를 사용. 처리량이 높은 스트림 이벤트를 관리
- 파디션 기반 설계 
- pull 모델 사용 
메세징 처리 방식 - 메시지 전달의 우선순위를 지정하는 범용 메세지 브로커
- 우선순위 대기열 지원
- 순서대로 메세지 처리
- Consumer가 메세지를 처리 → 브로커에 확인 (ACK) 응답 전송 → 브로커가 대기열에서 메시지 삭제 - 지연시간을 줄이고, 복잡한 메세지를 분산
- 우선순위 대기열 지원 X
- 토픽파티션을 사용하여 메세지 처리. 파티션 내부에서의 순서 보장 (offset)
- 메세지를 로그 파일에 추가. 보존기간이 만료될 때까지 보관. 로그파일에 있는 메세지는 언제든 데이터 처리 가능.
성능 - 초당 수천 개 메시지 처리 (그 이상은 다중 브로커 설정 필요)
- 지연시간이 짧다
- 초당 수백만 개의 메시지 처리
- 대용량 데이터를 처리한다
  • 어떤게 더 좋고 나쁜 것은 없다!
  • 구축하는 시스템의 요구사항에 더 적합한 것을 선택하는 것이 좋다. 

왜 MSA에서 쓰일까?

서비스간 비동기 통신

  • 서로 다른 서비스 간의 동기적인 호출이 많아지면 시스템 성능, 안정성에 영향
  • Kafka, RabbitMQ → 비동기 메시징을 통해 MSA간 통신을 비동기적으로 처리

확장성

  • MSA 환경에서는 증가하는 트래픽이나 데이터 양에 대응하여 성능을 유지시키거나 향상 시킬 수 있는 환경이 중요 합니다
  • Kafka → 파티셔닝 (수평적 확장), 클러스터에 브로커 추가 (다운타임 없이 추가 가능)
  • RabbitMQ → 클러스터링, 분산 큐

데이터 일관성, 신뢰성

  • 각 MSA는 독립적으로 데이터를 관리 (e.g. msa 별로 분리된 database 사용) → 서비스 간 데이터 일관성 유지 중요
  • Kafka → 로그, 메세지 저장 기능으로 데이터 일관성 유지, 메세지를 여러번 읽을 수 있도록 처리 가능
  • RabbitMQ → 메세지 디스크 저장, 다른 큐에 복제 하여 데이터 내구성 유지, 실패한 메세지 재처리 지원

서비스 간 결합도 ⬇️

  • Kafka → 이벤트 기반 아키텍처 지원. 서비스 간 독립적으로 동작 가능.
  • RabbitMQ → 큐 기반 메세징을 통해 서비스 간 메세지 전달.

요약  → RabbitMQ, Kafka는 MSA 에서 각각의 서비스가 독립적으로 동작하면서도 효율적으로 통신할 수록 지원하는 도구!

Kafka 를 MSA에 적용한다면

이벤트 중심 아키텍처 (Event-Driven Architecture)

  • 각 MSA간 통신을 이벤트 기반으로 처리. 통신을 담당하는 주체는 Kafka
  • 예시) 주문생성, 결제완료, 배송준비 등의 이벤트를 Kafka에 전달, 이를 구독하는 다른 서비스가 해당 이벤트를 처리하여 필요한 작업 수행

실시간 데이터 처리

  • 실시간으로 발생하는 대규모 데이터를 스트리밍 방식으로 처리
  • 예시) 사용자 행동로그, 모니터링 데이터를 실시간 분석 → 다른 서비스 전달

모니터링과 로깅

  • 중앙 집중식 로그 수집

이벤트 중심 아키텍처 Event-Driven Architecture 란

핵심 개념

이벤트

시스템 상태변화나 중요한 작업을 나타내는 객체. (e.g. OrderCreated, DeliveryCompleted)

 

이벤트 발행

MSA서비스에서 이벤트가 발생했을 때 (주문 생성 요청이 들어왔을 때), 해당 이벤트를 Kafka와 같은 메시징 시스템에 전달

 

이벤트 구독

  • 특정 이벤트에 관심 있는 MSA들이 해당 이벤트를 구독.
  • payment-msa는 order-msa가 발행한 OrderCreated 이벤트를 구독하고, 결제 처리 시작

비동기 처리

  • 이벤트를 발생한 MSA 서비스와 처리하는 MSA서비스가 비동기적으로 동작
  • order-msa는 OrderCreated 이벤트를 발행 후, 다른 MSA들이 처리하는 것을 기다리지 않고 (동기 X), 후에 필요한 비즈니스 로직 수행

코드 예시

build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.kafka:spring-kafka'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'

    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
        ...
}

application.yml

spring:
  application:
    name: order-msa
  datasource:
    url: jdbc:h2:mem:testdb
    driverClassName: org.h2.Driver
    username: sa
    password:
  h2:
    console:
      enabled: true
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  kafka:
    bootstrap-servers: localhost:9092
    consumer: # TODO kafka consumer, producer에 대한 설정값 수정은 여기에서
      group-id: ${spring.application.name}-group
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      properties:
        spring.json.trusted.packages: '*'
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringDeserializer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8083

이벤트 발신

  • KafkaTemplate을 사용해서 이벤트를 발신.
  • 토픽과 보내고자 하는 이벤트 내용을 담아서 전송.
    • e.g. 배송시작 시 → DeliveryCreatedEvent를 생성해서 Notification-Msa가 관련 알림을 보낼수 있게 처리
@Service
@RequiredArgsConstructor
public class DeliveryService {
    private final DeliveryRepository deliveryRepository;
        private final KafkaTemplate<String, Object> kafkaTemplate; // 자동 주입

    @Transactional
    public void createDelivery(...) {
                Delivery delivery = new Delivery(...);
                deliveryRepository.save(delivery);

                DeliveryCreatedEvent event = new DeliveryCreatedEvent(...); //이벤트에 필요한 값을 넣어주세요
        kafkaTemplate.send("delivery-created", EventSerializer.serialize(event));
    }
}

이벤트 수신

  • @KafkaListener 를 사용하여 메세지를 수신
  • groupId는 application.yml에서 설정한 consumer group id 
@Transactional
@KafkaListener(topics = "delivery-created", groupId = "notification-group")
public void handleDeliveryCreatedEvent(String message) {
    DeliveryCreatedEvent event = EventSerializer.deserialize(message, DeliveryCreatedEvent.class);

    // 이벤트로 해야하는 후 처리 진행 (비즈니스 로직)
    // e.g. 배달완료 이벤트 발행 
}

Event-Driven + Kafka 구현 예시 

간단한 이벤트 중심 아키텍처를 맛(?) 볼수 있는 예제 코드 여기서 확인

예시 코드 구조

기술스택

  • Spring-Kafka
  • Kafka, Kafka-UI, Zookeeper, Zipkin
  • Spring Cloud-Discovery Client, Server, Gateway
  • Spring JPA
  • H2

이벤트 흐름

 

코드베이스에 포함된 것:

  • docker-compose.yml (zookeeper, kafka, kafka-ui, zipkin)
    • 실행이 안된다면, 환경변수를 설정해주세요! DOCKER_DEFAULT_PLATFORM=linux/amd64
  • 주문 생성, 주문 목록 조회 http 테스트 파일
  • order-msa, payment-msa, notification-msa skeleton 코드, gateway, eureka
  • 객체 Serialize, De-Serialize 유틸클래스

코드가 제대로 실행되고 있는지 확인 하는 방법

✅ docker-compose 실행 후 컨테이너 확인

 

모든 msa가 정상적으로 실행되는지 확인 → Eureka 대시보드 (http://localhost:8761)

 

 Kafka UI 에서 topic, consumer가 정상 등록되어있는지 확인

 

  api-test.http 파일을 통해 주문생성 API 실행. 각 Topic에서 메세지가 제대로 들어오는지 확인

  • 주문 → 결제완료 메세지
  • 결제 → 주문생성 메세지
  • 알림 → 주문완료

 

 notification-msa에서 주문완료 된 로그 메세지 출력 확인

  •  

더 알아보고 싶다면 🤓

이벤트 중심 아키텍처를 적용했을 때 msa에서 transaction 관리는 어떻게 하지?

그 외, 읽어보면 좋은 글

반응형