Tech/Kafka

[Kafka] 하나의 토픽에 두개의 컨슈머가 구독하면 안되나요?

봄의 개발자 2024. 5. 16.
728x90
반응형

들어가며

개인 프로젝트에서 대규모 데이터 처리를 위해 카프카를 도입했다. MSA를 사용해 서비스를 분리했기 때문에 주문이 들어오면 각 서비스 간에 이벤트를 주고 받게 된다. 주문 취소 기능을 구현하다가 하나의 토픽에 대해 두개의 컨슈머가 구독하여 각 서비스에서 행동을 하도록 구현을 했다. 그러나 하나의 서비스에서만 이벤트를 받아서 동작하였다. 

이 문제를 분석하고 해결한 방식에 대해 작성할 예정이다.


 

문제 발생

발생한 문제에 대해서는 이전에 설명한 것과 같다. 다시 간단하게 요약하자면 하나의 토픽을 두개의 컨슈머가 구독한 상황에서 하나의 컨슈머만 이벤트를 처리하고 있다는 것이다.

처음에는 파티션이 1개여서 발생한 문제라고 추측을 했다. 그래서 파티션을 2개로 늘려서 다시 시도해봤지만 실패했다.

이때 실패한 이유는 파티션을 두개로 나눠도 메시지가 발행되면 파티션 하나에만 들어가기 때문에 문제를 해결할 수 없었던 것이다.

 

여기서 간단하게 개념을 짚어보면,

  • Consumer(소비자): 카프카 클러스터(쉽게 말해 카프카 서버)로부터 데이터를 가져오는 역할을 한다. 한 개 이상의 토픽을 구독하고, 해당 토픽으로부터 메시지를 읽어들여 처리한다. Consumer는 컨슈머 그룹을 형성할 수 있으며, 각 그룹 내에서는 파티션들이 그룹의 컨슈머들에게 고르게 할당된다.
  • Topic(토픽): 카프카에서 메시지를 분류하는 단위이다. 한 토픽 내에서도 데이터를 분산시키고 병렬 처리를 가능하게 하는 파티션으로 나뉜다.
  • Partition(파티션): 토픽 내에서 더 세분화된 데이터의 저장단위이다. 각 토픽은 하나 이상의 파티션으로 구성된다. 

 

일단 언급된 개념에 대해서만 설명했다. 좀 더 자세한 내용과 관련된 심화 내용은 아래 블로그를 참고하자. (면접 대비 질문 리스트지만 답변 보면서 충분히 카프카에 대해 공부할 수 있을 것이다.)

 

[Kafka] 면접 대비

[Kafka]Q. Apache Kafka란 무엇이며, 어떤 문제를 해결하기 위해 사용되나요?Apache Kafka는 LinkedIn에서 개발되어 현재는 Apache 소프트웨어 재단의 일부인 고성능, 분산 스트리밍 플랫폼이다. Kafka는 대규모

yenjjun187.tistory.com

 

 

 

다시 본론으로 돌아가 요약하면 하나의 토픽 안에 여러 개의 파티션이 있을 수 있다. 또한 하나의 파티션은 하나의 컨슈머만 구독할 수 있다. 

다음으로는 하나의 토픽에 대해 2개의 파티션으로 나뉘어있으며 2개의 컨슈머가 구독한 경우 동작 과정에 대해 살펴보자.

 

동작 과정:

1. 토픽 발행: 프로듀서가 토픽에 메시지를 발행한다. 이때 메시지는 두 개의 파티션에 균등하게 분산된다.
2. 컨슈머 그룹 생성: 두 개의 컨슈머가 하나의 컨슈머 그룹을 형성한다. 이 컨슈머 그룹은 토픽을 구독하게 된다.
3. 파티션 할당: 카프카는 컨슈머 그룹 내의 컨슈머들에게 파티션을 균등하게 할당한다. 이 경우 각 컨슈머가 한 개의 파티션을 할당받게 된다.
4. 메시지 소비: 각 컨슈머는 자신이 할당받은 파티션의 메시지를 소비하기 시작된다. 이때 메시지는 순서대로 처리된다.
5. 리밸런싱: 토픽의 파티션 수가 변경되거나, 컨슈머가 추가/제거되면 리밸런싱이 발생한다. 리밸런싱 과정에서 파티션 할당이 재조정된다.
6. 오프셋 관리: 각 컨슈머는 자신이 처리한 메시지의 오프셋을 관리한다. 이를 통해 메시지 처리 상태를 추적할 수 있다.

 

내가 예상했던 건 해당 토픽에 이벤트가 발행되면 각 파티션에서 이를 처리하기 위해 각 컨슈머로 전달되는 것이었다. 그러나 동작과정의 4번을 보면 알 수 있듯이 메시지가 순서대로 처리된다. 

 

결론을 이야기하자면 파티션을 2개로 나눈다고 해서 각 컨슈머에 이벤트가 발행되지 않는 문제를 해결할 수 없다는 것이다.

 


 

문제 원인

근본적인 문제 원인은 다른 곳에 있었다. 현재 프로젝트에서 모든 consumer configuration에서 동일한 consumer group id를 사용하고 있다. 그렇기에 두 개의 컨슈머 중 하나가 이를 처리하면 다른 컨슈머에서는 이미 처리된 이벤트이기 때문에 받아오지 않는 것이다. 

결국 각각 다른 컨슈머 그룹으로 분리해주어야 한다!

 

문제 해결 방법

각각 서비스에서 다른 컨슈머 그룹 아이디를 사용하도록 수정했다.

 

kafka property class

 

yml 파일

카프카 property를 관리하기 위해서 @ConfigurationProperties 어노테이션을 사용하게 되었다.

 

order service의 consumer configuration 클래스

 

각 서비스의 consumer configuration에서 GROUP_ID_CONFIG를 지정해줬다. 

 


 

결과

1. order-service에서 주문 취소 이벤트가 발행되었다. 이는 product, payment service로 전송된다.

 

2. product-service에서는 해당 이벤트를 받고 상품 재고를 증가시킨다.

 

3. payment-service에서도 동일한 이벤트를 받고 결제 엔티티의 상태를 변경해준다.

 

 

포스트맨 테스트 결과

 

    • 주문 직후

포스트맨 테스트
Redis

주문 직후에 레디스와 db를 확인해보면 기존에 270개 였던 재고가 260개로 줄었다.

Order

주문 상태를 보면 CREATED로 되어있다.

Delivery

배송 상태를 보면 READY_FOR_SHIPMENT(배송 준비 중) 상태로 되어있다.

Payment

결제 상태를 보아도 SUCCESS로 성공 상태이다.

 

  • 주문 취소 직후

Redis

주문 취소 후에 다시 270으로 재고가 증가되었음을 알 수 있다.

Order

주문 상태를 보면 CREATED -> CANCELED로 변경되었다.

 

Delivery

배송 상태를 보면 READY_FOR_SHIPMENT -> CANCELED 로 변경되었다.

 

Payment

결제 상태를 보면 SUCCESS ->  CANCELED 로 변경되었다.

 


마치며

카프카에 대한 개념이 부족해서 잘못된 방향으로 가기도 했었지만 결국 원인을 찾아서 해결했다. 카프카를 사용하면서 걱정이 많긴 했다. 면접 준비가 막막할 것 같아서 사용하고 싶지 않았는데 그래도 새로운 기술을 프로젝트에 직접 적용해보는 건 좋은 경험이니까... 

그래도 재밌고 이벤트 기반 동작이 신기하게 느껴진다!! 주문, 결제, 배송 엔티티마다 각각의 상태가 존재하는데 이를 하나의 흐름에서 변경하는 건 굉장히 어려운 일이었다. 게다가 MSA를 적용해 서비스가 분리된 상황이라 더욱 어려운 상태였다. 그러나 카프카를 도입하면서 이벤트 기반으로 처리가 가능해지니까 굉장히 편리했다. 사용자가 주문 요청을 하든 주문 취소 요청을 하든 서비스 간에 이벤트를 주고 받아 각 서비스가 해야하는 일을 처리한다는 게 너무너무 신기했다. 

728x90
반응형

댓글