Apache Kafka란 

현재 카프카는 Apache Software Foundation에서 오픈 소스로 관리하는 분산 이벤트 스트리밍 플랫폼입니다. 2011년 링크드인에서는 수많은 사용자 활동 로그와 시스템 이벤트를 실시간으로 처리하는 과정에서 많은 어려움을 겪었습니다. 당시 링크드인 개발자(Jay Kreps, Neha Narkhede, Jun Rao)는 카프카를 개발하게 되었습니다. 참고로 'Kafka'라는 이름은 프란츠 카프카의 소설에서 영감을 받아 붙여진 이름입니다.

당시 링크드인은 이벤트를 보내는 쪽(프로듀서)과 받는 쪽(컨슈머)이 각각 여러 개 존재했습니다. 각 프로듀서와 컨슈머가 서로 직접 연결되어야 했고 따라서 새로운 생산자나 소비자가 추가될 때마다 모든 기존 시스템과 새로 연결해야 해서 연결이 매우 복잡해졌습니다.

카프카 도입 이전 링크드인 시스템 구조 (https://www.confluent.io/blog/event-streaming-platform-1)

위 처럼 스파게티 파이프라인을 해결하기 위해 중앙화된 메시지와 데이터의 흐름을 관리하는 구조를 가져가기로 했고, 그 과정에서 만들어진 것이 카프카입니다.

카프카 도입 이후 링크드인 시스템 구조 (https://www.confluent.io/blog/event-streaming-platform-1)

LinkedIn에서의 이러한 카프카 사용은 엄청난 규모로 성장했습니다. 현재 LinkedIn 카프카는 여러 데이터 센터에 분산된 하루에 1조 개가 넘는 이벤트를 처리하고 있습니다. 카프카는 오픈 소스이기 때문에 이러한 방식은 링크드인을 넘어 수많은 회사에서 도입하여 사용하고 있습니다. 

아래에서는 카프카를 구성하는 여러 요소를 하나씩 설명하며 카프카 전체의 개념을 파악하고자 합니다.

1. 브로커 (Broker)

(https://docs.datastax.com/en/kafka/doc/kafka/kafkaHowMessages.html)

하나의 카프카 서버를 브로커(Broker)라고 합니다. 브로커는 프로듀서로부터 메시지를 수신하고 컨슈머의 파티션 읽기 요청에 메시지를 응답합니다. 이 때 컨슈머는 메시지를 읽을 때마다 오프셋을 이용하여 읽는 메시지의 위치를 알 수 있습니다.

(https://kajalrawal.medium.com/kafka-broker-kafka-topic-consumer-and-record-flow-in-kafka-ec55104977b8)

카프카의 브로커는 클러스터(Cluster)의 일부 구성원으로 동작하도록 설계되어있습니다. 여러개의 브로커가 하나의 클러스터에 포함될 수 있으며, 그 중 하나는 클러스터의 컨트롤러(Controller) 역할을 수행합니다. 컨트롤러는 클러스터 내의 각 브로커에게 담당 파티션을 할당하고, 브로커들이 정상적으로 동작하는지 모니터링합니다.

 

2. 토픽과 파티션 (Topic, Partition)

(https://support.smartbear.com/readyapi/docs/testing/kafka/index.html)

카프카의 메시지는 토픽(Topic)으로 분류됩니다. 토픽은 데이터베이스의 테이블이나 파일 시스템의 폴더와 유사합니다. 하나의 토픽은 여러 개의 파티션(Partition)으로 구성될 수 있습니다.

(http://cloudurable.com/blog/kafka-architecture-topics/index.html)

메시지는 파티션에 추가되는 형태로만 기록되며, 맨 앞부터 제일 끝까지의 순서로 읽히게 됩니다. 대개 하나의 토픽은 여러 개의 파티션을 갖지만, 메시지의 처리 순서는 토픽이 아닌 파티션별로 관리됩니다.

(https://developers.redhat.com/learning/learn:apache-kafka:kafka-101/resource/resources:what-are-partitions)

이 때 각 파티션은 서로 다른 브로커(서버)에 분산될 수 있는데, 이러한 특징 때문에 하나의 토픽이 여러 서버에 걸쳐 수평적으로 확장될 수 있습니다. 이는 단일 서버로 처리할 때보다 훨씬 높은 성능을 가지게 됩니다.

 

3. 메시지 (Message)

(https://velog.io/@hyun6ik/Apache-Kafka-Producer)

카프카에서는 데이터의 기본 단위를 메시지(Message)라고 합니다. 카프카는 메시지를 바이트 배열의 데이터로 간주하므로 특정 형식이나 의미를 갖지는 않습니다. 이 때문에 카프카에는 어떠한 데이터 형태든지 저장이 가능하고, 컨슈머쪽에서 메시지를 읽은 후 적절한 형태로 변환하여 사용해야 합니다.

(https://redpanda.com/guides/kafka-tutorial/kafka-partition-strategy)

카프카의 메시지는 토픽 내의 파티션에 기록됩니다. 이 때 특정 메시지를 기록할 파티션을 결정하기 위해 메시지에 담긴 키 값을 해시 처리하고, 그 값과 일치하는 파티션에 메시지를 기록하게 됩니다. 여기서 메시지의 키 값을 해시 처리하는 로직을 파티셔너(Partitioner)라고 합니다. 이러한 원리 때문에 동일한 키 값을 가지는 여러 개의 메시지는 항상 동일한 파티션에 기록되게 됩니다. 만약 메시지의 키 값이 null 로 전달된다면 카프카 내부의 기본 파티셔너는 토픽의 사용 가능한 파티션 중 하나로 무작위로 전송됩니다. 이러한 파티션 전략(strategy)을 다르게 설정하거나 커스텀하게 설정할 수 있습니다. 이에 대한 자세한 설명은 아래 링크에서 추가적으로 확인하시기 바랍니다.

https://redpanda.com/guides/kafka-tutorial/kafka-partition-strategy

 

Kafka Partition Strategy

Learn how to select the optimal partition strategy for your use case, and understand the pros and cons of different Kafka partitioning strategies.

redpanda.com

 

4. 프로듀서와 컨슈머 (Producer, Consumer)

카프카의 클라이언트는 프로듀서(Producer) 와 컨슈머(Consumer)가 있습니다. 프로듀서는 새로운 메시지를 특정 토픽에 생성합니다. 이 때 프로듀서는 기본적으로 메시지가 어떤 파티션에 기록되는지 관여하지 않습니다. 만약 프로듀서가 특정한 메시지를 특정한 파티션에 기록하고 싶을 때에는 메시지 키와 파티셔너를 활용할 수 있습니다. 파티셔너는 키의 해시 값을 생성하고 그것을 특정 파티션에 대응시키는데, 이러한 방식으로 지정된 키를 갖는 메시지가 항상 같은 파티션에 기록되게 유도할 수 있습니다.

(https://devlog-wjdrbs96.tistory.com/442)

컨슈머는 하나 이상의 토픽을 구독하면서 메시지가 생성된 순서로 읽게 됩니다. 컨슈머는 메시지를 읽을 때마다 파티션 단위로 오프셋을 유지하여 읽는 메시지의 위치를 알 수 있습니다. 또한 프로듀서는 메시지를 추가할 때 파티션의 마지막으로 넣은 오프셋을 알고 있습니다. 만약 프로듀서가 데이터를 넣는 속도보다 컨슈머가 데이터를 소비하는 속도가 느리다면 컨슈머가 마지막으로 읽은 오프셋과 프로듀서가 마지막으로 넣은 오프셋의 차이가 발생합니다. 이 차이를 Consumer Lag이라고 합니다. 만약 Consumer Lag이 높다면 컨슈머가 프로듀서에 의해 생성된 메시지를 제때 처리하지 못하고 뒤처지고 있다는 것을 의미합니다.

(https://ibm-cloud-architecture.github.io/refarch-eda/technology/kafka-consumers/)

 

5. 컨슈머 그룹 (Consumer Group)

(https://dev.to/joaosczip/apache-kafka-what-is-and-how-it-works-gf9)

카프카 컨슈머들은 컨슈머 그룹(Consumer Group)에 속하게 됩니다. 여러 개의 컨슈머가 같은 컨슈머 그룹에 속할 때에는 각 컨슈머가 해당 토픽의 다른 파티션을 분담해서 메시지를 읽을 수 있습니다. 이처럼 하나의 컨슈머 그룹에 더 많은 컨슈머를 추가하면 카프카 토픽의 데이터 소비를 확장할 수 있습니다. 즉, 더 많은 컨슈머를 추가하는 것은 메시지 소비 성능 확장의 중요한 방법입니다.

4개 파티션, 2개 컨슈머
4갸 파티션, 4개 컨슈머. 최대 읽기 성능을 가지게 됩니다.
4개 파티션, 5개 컨슈머. 1개의 컨슈머는 소비할 수 없습니다.

출처 https://scrutinybykhimaanshu.blogspot.com/2019/12/consumer-inside-group.html

이 때 주의할 점은 하나의 토픽의 각 파티션은 한개의 컨슈머만 처리할 수 있습니다. 그렇기 때문에 하나의 토픽 내의 파티션 개수보다 더 많은 수의 컨슈머를 추가하는 것은 의미가 없다는 것을 명심해야 합니다. 그리고 각 컨슈머가 특정 파티션에 대응되는 것을 파티션 소유권(Partition Ownership) 이라고 합니다.

하나의 토픽에 여러 개의 컨슈머 그룹이 메시지를 읽을 수 있습니다.

카프카는 하나의 토픽에 여러 개의 컨슈머 그룹이 붙어서 메시지를 소비할 수 있는 다중 컨슈머 기능을 제공합니다. 여러 개의 컨슈머 그룹이 서로간의 상호작용 없이 각자의 오프셋으로 각자의 순서에 맞게 메시지를 읽고 처리할 수 있습니다. 같은 토픽의 메시지를 읽어야 하는 여러 개의 애플리케이션이 있다면 각각의 애플리케이션마다 각자의 컨슈머 그룹을 갖도록 하면 됩니다. 이 때문에 보통 컨슈머 그룹명은 애플리케이션 이름과 일치시키거나 MSA 환경일 경우 팀 이름으로 관리합니다.

 

6. 리플리케이션 (Replication)

복제(Replication)는 카프카 클러스터의 가용성을 보장하는 개념입니다. 카프카의 메시지는 토픽에 저장되며, 각 토픽은 여러 파티션으로 구성됩니다. 이 때 각각의 파티션은 다수의 복제본(Replica)을 가질 수 있습니다. 리플리카는 아래 두 가지 형태로 존재합니다.

리더 리플리카(Leader Replica) : 각 브로커의 동일한 토픽의 파티션은 리더로 지정된 하나의 리플리카를 가집니다. 일관성을 보장하기 위해 모든 프로듀서와 컨슈머 클라이언트의 요청은 리더를 통해서 처리됩니다. 즉, 모든 메시지의 읽기와 쓰기 요청은 리더 리플리카를 통해서만 처리됩니다.

팔로워 리플리카(Follower Replica) : 각 브로커의 동일한 토픽의 파티션의 리더를 제외한 나머지를 리플리카를 팔로워라고 합니다. 팔로워는 클라이언트의 요청을 서비스하지 않습니다. 단순히 리더의 메시지를 복제하여 리더의 것과 동일하게 유지하는 역할만 하게됩니다. 이후 특정 파티션 리더 리플리카가 중단되는 경우에는 팔로워 리플리카 중의 하나가 해당 파티션의 새로운 리더로 선출됩니다.

최고의 그림 (https://log-laboratory.tistory.com/234)

리더 리플리카와 동기화 하기 위해 팔로워 리플리카들은 리더에게 Fetch 요청을 전송합니다. 이것은 컨슈머가 메시지를 읽기 위해 전송하는 것과 같은 요청 타입입니다. 이 때 최신 메시지를 계속 요청하는 팔로워 리플리카를 In-Sync Replica(ISR)이라 합니다. 당연하게도 ISR에 속하지 않은 리플리카는 추후 리더에 장애가 생겼을 때 새로운 리더가 될 수 없습니다.


출처

 

Apache Kafka 간략하게 살펴보기

본 문서는 “카프카 핵심 가이드 (제이펍)” 를 참고하였습니다.

medium.com