최근 많은 시스템이 단순한 메시지 큐를 넘어, 대용량 데이터 처리와 실시간 스트리밍이 가능한 이벤트 기반 아키텍처로 진화하고 있습니다. 이러한 흐름의 중심에 있는 기술이 바로 Apache Kafka입니다.
기존에 RabbitMQ를 사용하던 Spring Boot 애플리케이션을 Kafka로 전환하는 것은 단순히 라이브러리를 교체하는 것 이상의 의미를 가집니다. 이는 시스템의 확장성과 데이터 처리 능력을 한 단계 끌어올리는 중요한 아키텍처 개선 과정입니다.
이 가이드에서는 RabbitMQ를 Kafka로 직접 전환하는 전체 과정을 단계별로 상세히 안내합니다.
1단계: 기존 RabbitMQ 의존성 제거
전환의 첫 단계는 기존 RabbitMQ와의 연결을 완전히 끊어내는 것입니다. 코드베이스에 남아있는 RabbitMQ 관련 설정은 잠재적인 충돌을 유발할 수 있으므로 명확하게 정리해야 합니다.
- Bean 제거:
@Configuration
클래스에 정의했던Queue
,Exchange
,Binding
관련 Bean과RabbitTemplate
Bean을 모두 삭제합니다. - Listener 제거:
@RabbitListener
어노테이션이 붙은 모든 메소드와 관련 클래스를 삭제하거나 주석 처리합니다. - 의존성 라이브러리 제거:
build.gradle
또는pom.xml
에서spring-boot-starter-amqp
의존성을 제거합니다.
build.gradle (제거)
// 제거 대상
// implementation 'org.springframework.boot:spring-boot-starter-amqp'
2단계: Kafka 의존성 추가
이제 프로젝트에 Kafka 클라이언트 라이브러리를 추가할 차례입니다. Spring Boot 환경에서는 spring-kafka
의존성을 추가하면 손쉽게 Kafka 연동이 가능합니다.
build.gradle (추가)
dependencies {
implementation 'org.springframework.kafka:spring-kafka'
}
3단계: Kafka 연동 설정 (application.yml)
애플리케이션이 Kafka 클러스터와 통신하기 위한 필수 정보를 application.yml
파일에 설정합니다.
bootstrap-servers
: 접속할 Kafka 클러스터의 호스트 및 포트 정보입니다.consumer.group-id
: 컨슈머를 그룹화하는 ID입니다. 동일 그룹 ID 내에서는 하나의 파티션에 하나의 컨슈머만 접근하여 메시지를 병렬로 처리할 수 있습니다.consumer.auto-offset-reset
: 컨슈머가 처음 그룹에 참여했을 때 읽을 메시지 위치를 정합니다.earliest
는 가장 오래된 메시지부터,latest
는 가장 최신 메시지부터 읽습니다.key/value-serializer
,key/value-deserializer
: Producer와 Consumer가 메시지를 주고받을 때 사용할 직렬화/역직렬화 방식을 지정합니다. 가장 기본적으로는 문자열(String)을 사용합니다.
spring:
kafka:
bootstrap-servers: localhost:9092 # Kafka 서버 주소
consumer:
group-id: my-group
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
4단계: Kafka Producer 구현
메시지를 Kafka 토픽으로 발행(Publish)하는 Producer를 작성합니다. Spring Kafka가 제공하는 KafkaTemplate
을 주입받아 사용하면 매우 간단하게 구현할 수 있습니다.
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
@Service
public class KafkaMessageProducer {
private final KafkaTemplate<String, String> kafkaTemplate;
// 생성자 주입
public KafkaMessageProducer(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
/**
* 지정된 토픽으로 메시지를 전송합니다.
* @param topic 타겟 토픽 이름
* @param message 전송할 메시지
*/
public void send(String topic, String message) {
kafkaTemplate.send(topic, message);
System.out.println("Kafka Producer sent message: " + message);
}
}
5단계: Kafka Consumer 구현
토픽의 메시지를 구독(Subscribe)하여 소비하는 Consumer를 작성합니다. @KafkaListener
어노테이션을 사용하면 특정 토픽에 메시지가 들어왔을 때 해당 메소드가 자동으로 실행됩니다.
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@Component
public class KafkaMessageConsumer {
/**
* 지정된 토픽의 메시지를 소비합니다.
* @param message 수신한 메시지
*/
@KafkaListener(topics = "my-topic", groupId = "my-group")
public void consume(String message) {
System.out.println("Consumed message: " + message);
// 여기에 메시지 수신 후 처리할 비즈니스 로직을 구현합니다.
}
}
6단계: 서비스 로직 변경 및 적용
마지막으로, 기존에 RabbitTemplate
을 사용해 메시지를 보내던 부분을 새롭게 만든 KafkaMessageProducer
로 교체합니다.
기존 코드 (RabbitMQ)
// rabbitTemplate.convertAndSend("my-exchange", "my-routing-key", "Hello RabbitMQ!");
변경 후 코드 (Kafka)
// 서비스 클래스 내에서 Producer를 주입받아 사용
@Autowired
private KafkaMessageProducer kafkaMessageProducer;
public void someServiceMethod() {
// ... 비즈니스 로직 ...
kafkaMessageProducer.send("my-topic", "Hello Kafka!");
}
성공적인 전환을 위한 핵심 고려사항
단순한 코드 변경을 넘어 Kafka의 특성을 잘 활용하기 위해 다음 사항들을 추가로 고려하는 것이 좋습니다.
1. 아키텍처 관점의 변화: 큐에서 이벤트 스트림으로
RabbitMQ가 ‘할 일(Task)’을 큐에 넣어두는 방식에 가깝다면, Kafka는 ‘발생한 사건(Event)’을 시간 순서대로 기록하는 로그에 가깝습니다. 따라서 “주문 처리 요청”이라는 Task를 보내는 대신 “주문이 생성됨”이라는 이벤트를 발행하는 방식으로 사고를 전환하면 Kafka의 장점을 극대화하고, 더욱 유연한 시스템을 구축할 수 있습니다.
2. 성능 극대화: 파티션의 이해
Kafka의 높은 처리량은 ‘파티션(Partition)’을 통한 병렬 처리에서 나옵니다. 하나의 토픽을 여러 개의 파티션으로 나누고, 컨슈머 그룹 내의 컨슈머들이 각 파티션을 하나씩 맡아 동시에 메시지를 처리할 수 있습니다. 대용량 트래픽이 예상된다면 토픽의 파티션 수를 적절히 설계하는 것이 중요합니다.
3. 객체(DTO) 전송: JSON 메시지 활용
문자열 대신 복잡한 데이터 객체(DTO)를 주고받아야 할 경우, JSON 형식을 사용하는 것이 일반적입니다. Spring Kafka는 Jackson 라이브러리와의 통합을 지원합니다.
application.yml
에 아래 설정을 추가하고,
spring:
kafka:
producer:
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
consumer:
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: "*" # 신뢰할 수 있는 패키지 설정
KafkaTemplate
과 @KafkaListener
의 제네릭 타입을 해당 DTO 클래스로 지정하면 됩니다.
// Producer
private final KafkaTemplate<String, OrderDto> kafkaTemplate;
// Consumer
@KafkaListener(topics = "order-topic", groupId = "order-group")
public void consume(OrderDto order) {
// ...
}
RabbitMQ에서 Kafka로의 전환은 단순히 기술 스택을 바꾸는 것을 넘어, 시스템을 더욱 견고하고 확장 가능하게 만드는 전략적인 선택입니다. 이 가이드가 여러분의 성공적인 마이그레이션 여정에 든든한 디딤돌이 되기를 바랍니다. Kafka를 통해 데이터 기반의 실시간 애플리케이션이라는 새로운 가능성을 열어보세요.