Data/Kafka

Kafka 이해하기: 핵심 개념과 작동 원리

누구세연 2024. 11. 17. 16:45

MSA 환경에서는 서비스 간 데이터 전달과 비동기 처리를 위해 Apache Kafka와 같은 메시징 시스템이 자주 사용됩니다. Kafka는 높은 처리량, 확장성, 그리고 안정성을 바탕으로 대규모 데이터 스트리밍 및 실시간 처리에 탁월한 도구로 자리 잡고 있습니다.

 

이번 글에서는 MSA 환경에서 Kafka가 유용하게 사용되는 이유를 살펴보고, Kafka의 핵심 개념과 작동 원리를 간단히 정리하고자 합니다!

 

kafka

 

kafka란 무엇인가요?

Kafka는 분산 메시징 플랫폼으로, 실시간 데이터 스트리밍 및 대규모 데이터 처리를 지원합니다.

다음과 같은 특징을 갖고 있습니다.

 

  • 분산 아키텍처: 고가용성과 확장성 제공
  • 고성능: 초당 수백만 건의 메시지 처리 가능
  • 내구성: 디스크 기반 저장으로 데이터 유실 방지
  • 다양한 활용성: 실시간 데이터 처리 및 배치 처리 모두 지원

사용 사례

 

  • 로그 수집 및 모니터링
  • 데이터 파이프라인 구축
  • 실시간 이벤트 스트리밍

 

kafka 주요 요소

kafka는 여러 요소들과 함께 작동합니다.

  1. Producer(생산자)
    데이터를 만들어 보내는 역할입니다.
    - 예: 주문 데이터를 kafka에 전송
  2. Consumer(소비자)
    데이터를 받아 사용하는 역할입니다.
    - 예: 주문 데이터를 받아 상품 준비를 시작
  3. Broker(중간관리자)
    Producer가 보낸 데이터를 저장하고 Consumer에게 전달합니다.
    - 예: 중간 창고 역할
  4. Topic(주제)
    Producer가 보낸 데이터를 저장하는 폴더 같은 개념입니다.
    - 예: order-topic에 주문 데이터를 저장
  5. Partition(분할)
    Topic 데이터를 나누어 저장하는 공간입니다.
    - 데이터를 나눠서 처리하니 더 빠르고 효율적입니다.
    - 예: 100만 개 데이터를 4개로 나눠 처리
  6. Zookeeper
    kafka 클러스터(여러 대의 kafka 서버)가 잘 작동하도록 돕는 프로그램입니다.
    - kafka 2.8 버전 이후엔  Zookeeper 대신 KRaft라는 기술을 사용 중이에요.

 

Kafka의 주요 프로세스

Kafka의 동작 흐름을 간단히 정리합니다

Kafka의 주요 프로세스

1) 메시지 생성과 전송 (Producer → Broker)

  1. Producer가 토픽을 지정해 메시지를 전송.
  2. 메시지 키에 따라 특정 Partition에 저장.
  3. 메시지는 Broker의 로그 디렉터리에 파일로 기록.

2) 메시지 소비 (Broker → Consumer)

  1. Consumer는 특정 토픽의 Partition을 구독.
  2. Partition의 Offset 기반으로 데이터를 순차적으로 읽음.
  3. Consumer Group 내 각 Consumer는 서로 다른 Partition을 병렬로 처리.

3) 데이터 저장 및 복제

  1. Partition은 복제(replication)를 통해 데이터 내구성을 보장.
  2. 하나의 Partition에 Leader와 여러 Follower가 존재.
  3. Leader가 읽기/쓰기 요청을 처리하며, Follower는 동기화.

 

Kafka의 주요 개념 심화

1) 메시지 오프셋 (Offset)

  • Partition 내에서 메시지를 고유하게 식별.
  • Consumer는 마지막으로 읽은 Offset을 저장해 데이터 처리 상태를 유지.

2) Consumer Group

  • 같은 그룹의 Consumer는 토픽 Partition을 나눠 병렬 처리.
  • Consumer Group을 활용하면 확장성과 장애 복구 가능.

3) Replication (복제)

  • Kafka는 내구성을 위해 데이터를 복제.
  • Leader와 Follower는 클러스터 내에서 고가용성을 보장.

4) Acknowledgment (ACK)

  • Producer는 메시지 전송 후 Broker의 확인(ACK)을 받음.
  • 설정에 따라 acks=0, 1, all로 안정성을 조정.

 

Kafka 사용 시 고려사항

  • 파티션 설정: 적절한 Partition 수를 설정해 병렬 처리와 성능 최적화
  • Offset 관리: Consumer가 처리한 마지막 메시지를 추적해 데이터 유실 방지
  • 복제 설정: Leader와 Follower 구조로 데이터 안정성을 보장
  • 데이터 스키마: 메시지 형식을 표준화(예: Avro, JSON, Protobuf)해 데이터 일관성 유지

 

Kafka의 장점과 단점

장점

  • 대규모 데이터 처리 성능.
  • 장애 복구 및 확장성.
  • 다양한 데이터 파이프라인 구성 가능.

단점

  • 설정 복잡도.
  • 클러스터 관리와 모니터링 필요.
  • 특정 상황에서 높은 딜레이 발생 가능.

 

Kafka의 코드 예제

Producer 코드 예제

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

Producer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("test-topic", "key", "value"));
producer.close();

 

Properties 설정

  • bootstrap.servers:
    • Kafka 브로커의 주소를 지정합니다.
    • Kafka 클러스터가 여러 브로커로 구성되어 있다면, 한두 개만 명시해도 나머지를 자동으로 발견합니다.
    • 여기서는 로컬에서 실행 중인 브로커(localhost:9092)를 사용했습니다.
  • key.serializer와 value.serializer:
    • 메시지의 KeyValue를 직렬화할 클래스입니다.
    • Kafka는 네트워크 전송을 위해 데이터를 바이트 형태로 직렬화해야 합니다.
    • StringSerializer는 문자열 데이터를 바이트로 변환합니다.

KafkaProducer 생성

  • KafkaProducer<>(props):
    • 위에서 설정한 Properties 객체를 기반으로 Kafka Producer를 생성합니다.
    • 제네릭 타입 <String, String>은 메시지의 KeyValue가 각각 String임을 나타냅니다.

메시지 전송

  • ProducerRecord:
    • Kafka의 메시지는 ProducerRecord 객체로 표현됩니다.
    • 생성 시 토픽 이름, 메시지 키, 메시지 값을 지정합니다.
    • 예: new ProducerRecord<>("test-topic", "key", "value")
      • test-topic: 메시지가 저장될 토픽 이름.
      • key: 메시지를 특정 파티션에 매핑하는 데 사용.
      • value: 실제 전달할 데이터.
  • send 메서드:
    • 메시지를 지정한 브로커로 전송합니다.
    • 비동기적으로 처리되며, 필요하면 콜백을 추가해 결과를 확인할 수 있습니다.

Producer 닫기

  • producer.close():
    • 모든 리소스를 해제합니다.
    • 브로커로 전송되지 않은 메시지가 있다면 전송을 완료한 후 종료됩니다.

 

Consumer 코드 예제

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("test-topic"));

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
    for (ConsumerRecord<String, String> record : records) {
        System.out.printf("Offset: %d, Key: %s, Value: %s%n", record.offset(), record.key(), record.value());
    }
}

Properties 설정

  • bootstrap.servers:
    • Producer와 동일하게 브로커 주소를 설정합니다.
  • group.id:
    • Consumer Group을 지정합니다.
    • 같은 그룹에 속한 Consumer는 서로 다른 Partition을 병렬로 처리합니다.
    • 예: test-group은 하나의 Consumer Group 이름입니다.
  • key.deserializer와 value.deserializer:
    • 메시지의 KeyValue를 역직렬화할 클래스입니다.
    • StringDeserializer는 바이트 데이터를 문자열로 변환합니다.

KafkaConsumer 생성

  • KafkaConsumer<>(props):
    • 설정된 Properties를 기반으로 Kafka Consumer를 생성합니다.
    • 제네릭 타입 <String, String>은 메시지의 KeyValue가 각각 String임을 나타냅니다.

토픽 구독

  • consumer.subscribe:
    • 특정 토픽을 구독합니다.
    • 예: Collections.singletonList("test-topic")은 단일 토픽 test-topic을 구독합니다.

메시지 소비

  • poll 메서드:
    • Consumer는 브로커에서 데이터를 가져오기 위해 주기적으로 poll을 호출해야 합니다.
    • 예: Duration.ofMillis(100)은 100ms 동안 기다렸다가 메시지를 가져옵니다.
  • ConsumerRecords:
    • 여러 개의 메시지 묶음(records)을 반환합니다.
  • ConsumerRecord:
    • 각 메시지의 상세 정보를 포함.
    • 예:
      • record.offset(): Partition 내 메시지의 고유 위치.
      • record.key(): 메시지 키.
      • record.value(): 메시지 값.

무한 루프

  • Consumer는 while (true) 루프 안에서 실행됩니다.
    • 이는 데이터를 지속적으로 소비하기 위한 구조입니다.
    • 필요에 따라 종료 조건을 추가할 수 있습니다.

 

💡 Kafka는 실시간 데이터 처리와 확장성 면에서 뛰어난 도구로, MSA 환경뿐만 아니라 로그 수집, 이벤트 스트리밍, 데이터 파이프라인 등 다양한 영역에서 활용됩니다.
이 글이 Kafka를 이해하고 실무에 적용하는 데 도움이 되길 바랍니다! 😊