1. Introduction
카프카는 Pub/Sub 메시징 시스템에서 메시지를 중개하는 일종의 브로커 역할을 한다.
여기서는 카프카 입문을 위한 몇가지 기본적인 컨셉들을 왜 카프카를 사용해야 하는지, 그리고 카프카 활용 케이스에 대해 다룬다.
카프카 도입의 이유
카프카 도입의 이유
메시지 생산자와 소비자가 하나만 있을 경우 가운데 간단한 메시지 큐나 프로세스간 통신 채널을 놓는 것 만으로 서비스를 구축할 수 있다. 그러나 서비스가 확장되면서 메시지 큐가 늘어나면 중복이 발생하고, 다수의 큐를 따로 관리하면 유지보수에 어려움이 생긴다. 이러한 문제점을 해결하기 위해, 개별 메시지 큐가 아닌 중앙 집중된 메시지 큐가 필요해졌다. 다수의 큐가 여러개 있는 시스템 하에서는 복잡한 데이터 파이프라인을 효율적으로 처리하기 어렵고 대규모 확장이 힘들었기 때문에, 데이터 파이프라인 일원화와 높은 처리량, 확장성을 갖춘 새로운 메시징 플랫폼으로 Kafka가 탄생하게 되었다
2. 카프카 입문을 위한 몇가지 컨셉들
메시지와 배치
- 카프카에서 메시지는 키와 값으로 구성된다.
- 카프카의 메시지는 기본적으로 바이트 배열로 구성되어 있다.
- 카프카는 효율적인 메시지 전달을 위해 여러 개의 메시지를 배치로 처리한다.
- 기본적으로 배치의 크기가 커질수록 Throughput은 증가하지만, Latency 또한 증가한다.
- Latency와 Throughput 사이의 Trade-off를 고려하여 적절한 배치 크기를 설정할 필요가 있다.
키에서 생성한 hash 값으로 메시지의 파티션을 결정하는 예제
public class CustoaPartitioner implements Partitioner {
@Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueTypes, Cluster cluster) {
if (keyBytes == null) { throw new InvalidRecordException("Message key is null"); }
if (((String) key).equals("test")) { return 0; // specific partition }
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); // get partition list of specific topic int numPartitions = partitions.size(); return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions; // positive hash value of keyBytes into numPartition
}Concept
- Record : 카프카에서 데이터의 단위. Record는 Key, Value, Timestamp로 구성된다.
- Key : 메시지를 구분하는 식별자로, 파티션을 결정하는데 사용된다.
- Value : 메시지의 실제 데이터를 담고 있다.
- Timestamp : 메시지의 생성 시간을 나타낸다.
- Batch : 단순히 같은 토픽에 속한 메시지의 집합을 의미한다.
스키마
- 카프카에서 스키마는 메시지에 대한 명세이다.
- 일반적으로 직렬화 형식을 제공하며 메시지와 스키마를 분리하여 관리하는 Avro 포맷이 선호된다.
Concept
- 스키마 : 카프카에서 스키마는 메시지의 구조를 담고 있는 일종의 명세다. 스키마의 핵심은 데이터의 저장및 전송을 위한 일관적인 특정 형식을 지원하는 것이다.
토픽과 파티션
- 카프카에서 토픽은 논리적인 메시지의 구분 단위이만 파티션은 물리적인 메시지의 구분 단위이다.
- 토픽은 어떤(What) 메시지를 전달할지, 파티션은 어떻게(How) 메시지를 전달할지를 결정한다고 볼 수 있다.
Concept
- Topic : 카프카에서 메시지의 논리적인 구분단위. 일종의 카테고리나 디렉토리로 생각할 수 있다. 토픽은 여러개의 파티션으로 구성되어 있다. 토픽은 메시지의 논리적 연관성이나 데이터 처리 패턴에 따라 결정될 수 있다.
- Partition : 파티션은 메시지의 물리적인 분할 단위이다. 보통 브로커에 분산 저장되며 각각 독립적인 offset을 가지기에 순서가 보장되는 특성이 있다.
프로듀서와 컨슈머
- 프로듀서와 컨슈머를 묶어서 카프카 클라이언트라고 한다.
Concept
- Producer : 원천 데이터에서 메시지를 생성해 카프카에 전송하는 역할을 하는 클라이언트.
- Partitioner : 프로듀서에서 메시지를 전송할 때, 메시지를 어떤 파티션에 저장할지 결정하는 역할을 하는 클래스. 특정 키를 가진 메시지를 특정 파티션에 저장하거나 동일한 키를 가진 메시지를 동일한 파티션에 저장하는 등의 역할을 한다.
- Consumer : 카프카에서 메시지를 소비하는 역할을 하는 클라이언트.
브로커와 클러스터
Concept
- Kafka Broker : 데이터 수집과 분산처리를 담당하는 카프카의 서버 단위. 주로 프로듀서로 부터 데이터를 수신해서 파티션에 저장하고 , 팔로워 브로커에게 데이터를 복제하고, 컨슈머에게 데이터를 전달하는 역할을 한다.
- Kafka Cluster : 여러개의 브로커로 구성된 카프카의 집합. 클러스터는 데이터의 복제와 분산처리를 위한 여러개의 브로커로 구성된다.
- Controller : 카프카 클러스터의 상태를 관리하고 조정하는 브로커의 일종. 브로커 내에 존재하는 리더 파티션 재분배. 파티션 리밸런싱, Broker 상태 관리 등의 역할을 수행한다.
- Replication : 카프카에서 데이터 복제는 팔로워 파티션이 리더 파티션의 오프셋을 따라 동기화 되는 것을 의미한다.
- 2f + 1 rule : f대의 브로커의 장애를 허용하려면 . 전체 브로커의 수가 최소 2f + 1 이상이어야 한다. 이는 한개 노드가 장애가 나도 나머지 두 개 node가 동기화를 유지할 수 있도록 하기 위함이다. 예를 들어 2개의 브로커가 있을 경우 한개의 브로커가 장애가 나면 나머지 한개의 브로커가 동기화를 유지할 수 없기 때문에 데이터 유실이 발생할 수 있다.
카프카 브로커가 최소 3대 이상이어야 하는 이유
카프카 브로커가 최소 3대 이상이여야 하는 이유는 기본적으로 가용성과 안정성을 보장하기 위해서 이다. 카프카 클러스터의 안정성을 보장하기 위해 리더 브로커와 팔로워 브로커가 동기화된 In Sync Replica (ISR)를 유지해야 한다. 만약 리더 브로커가 다운될 경우 ISR에 있는 팔로워 브로커 중 하나가 리더 브로커로 승격되어야 한다. 이 때 ISR에 있는 팔로워 브로커가 2대 이상이어야만 리더 브로커로 승격할 수 있다. 이는 브로커 간 데이터 복제 속도에 따른 시간차이로 인한 동기화 과정에서의 데이터 유실 가능성 에 기인한다. 따라서 카프카 브로커가 최소 3대 이상이어야 ISR에 있는 팔로워 브로커가 2대 이상이 되어 리더 브로커로 승격할 수 있다.
다중 클러스터
다음 요구 사항에 따라 다중 클러스터 구성을 고려할 수 있다.
- 보안 요구사항을 충족시키기 위한 격리
- 재해 복구를 위한 백업 구성
카프카 클러스터 간 데이터 복제를 위해 MirrorMaker를 사용할 수 있다.
3. 카프카를 사용해야 하는 이유
카프카 구조 자체에서 오는 장점을 3가지로 정리할 수 있다.
- 다중 클라이언트를 통한 병렬 처리
- 디스크 기반 보존
- 확장성
다중 클라이언트를 통한 병렬 처리
- 프로듀서 : 여러 프로듀서로부터 병렬로 일괄적인 메시지 수집
- 컨슈머 : 컨슈머 그룹을 통한 병렬 처리 -> 하나의 스트림에 대해 여러 컨슈머가 병렬 처리 가능
디스크 기반 보존
- 디스크 기반 메시지 저장 : 메시지를 디스크에 저장하여 메시지 유실을 방지 -> 내구성 보장
- 메시지 우선순위 : 메시지가 필요에 따라 서로 다른 기간안 보존 가능
확장성
- 유연한 확장성 : 브로커를 추가하여 확장 가능
4. 카프카 활용 케이스
- 활동 모니터링
- 로그 수집
- 지표 수집
- 스트림 처리
- 메시지 교환
References
- 카프카 핵심 가이드