Event streaming/Kafka

2. Kafka > 기초 개념

Krevis 2024. 6. 21. 14:53

특징

Distributed system

분산 시스템은 네트워크상에서 연결된 컴퓨터들의 그룹으로, 단일 시스템이 갖지 못한 높은 성능을 목표로 한다

장애 대응에 유리

부하가 높을 때 시스템 확장 용이

 

카프카의 브로커는 온라인 상태에서 간단하게 추가할 수 있다

Page cache

높은 처리량을 위해 OS의 페이지 캐쉬를 활용한다

 

디스크에 읽고 쓰는 대신 물리 메모리 중 애플리케이션이 사용하지 않는 잔여 메모리를 활용

Batch 전송 처리

카프카는 클라이언트들과 수많은 통신을 하는데, 이를 묶어서 처리하면 네트워크 오버헤드를 줄일 수 있다

압축 전송

메시지 전송 시 높은 압축 전송 사용을 권장한다

 

네트워크 대역폭이나 회선 비용 등을 줄일 수 있다

 

파일 하나를 압축하는 것보다 여러 개를 압축하는 쪽의 효율이 더욱 좋다

 

압축 타입별 특성

  • 높은 압축률 필요: gzip, zstd
  • 빠른 응답 속도 필요: lz4, snappy

실제로 메시지를 전송해보면서 테스트 해보고 결정하는 것이 가장 좋다

주키퍼의 의존성

지노드(Znode)를 이용해 카프카의 메타 정보가 기록되며, 브로커의 노드/토픽/컨트롤러 관리 등을 한다

 

최근 들어 카프카가 성장하면서 주키퍼의 성능 한계가 드러나 주키퍼의 의존성을 제거하려는 과정을 거치고 있다

 

 

Replication

메시지들을 복제해 클러스터 내 브로커들에 분산시키는 것

 

replication-factor가 3이면 원본을 포함해 3개의 복제가 있다는 것이다

  • some-kafka-1
    • some-topic-1
  • some-kafka-2
    • some-topic-1
  • some-kafka-3
    • some-topic-1

 

복제 단위는 토픽이 아닌 파티션이다

 

적정 복제 인수

  • 테스트, 개발 환경: 1
  • 운영 환경(로그성 메시지, 약간의 유실 허용): 2
  • 운영 환경(유실 비허용): 3

 

 

Consumer

Lag

뒤에 처지다, 뒤떨어지다

 

Lag = 생산자가 보낸 메시지 수(카프카의 잔여 메시지 수) - 소비자가 가져간 메시지 수

결국 소비자의 처리 지연을 나타낸다

 

 

Partition

토픽을 파티션으로 나누면, 나뉜 수만큼 소비자와 연결할 수 있다

 

파티션 번호는 0부터 시작한다

 

토픽 생성 시 파티션 수를 설정할 수 있다

  • 적정 파티션 수를 구하는 공식
    • 각 메시지 크기나 초당 메시지 건수 등에 따라 달라지므로 정확하게 예측하기 어렵다
    • 파티션 수는 도중에 늘릴 수는 있지만, 줄일 수는 없다
    • 따라서 초기에 작게, 즉 2 또는 4 정도로 생성 후, 메시지 처리량이나 소비자의 Lag 등을 모니터링하면서 늘려가는 방법이 가장 좋다

 

 

Segment

생산자가 브로커로 전송한 메시지는 토픽의 파티션에 저장된다

 

각 메시지들은 브로커의 로컬 디스크에 세그먼트라는 로그 파일의 형태로 저장된다

 

2024.06.19 - [Event streaming/Kafka] - Kafka > 시작하기

위 글 기반으로 카프카 준비가 되었다고 가정하겠다

 

docker exec -it some-kafka-1 /bin/bash

 

kafka-topics --bootstrap-server localhost:19092 --create --topic some-topic-2

 

kafka-console-producer --bootstrap-server localhost:19092 --topic some-topic-2

>Hello, world!

Contrl + c로 종료

 

이제 호스트 OS에서 데이터를 확인해보자

 

ll data/kafka-1

ll data/kafka-2

ll data/kafka-3

 

데이터가 생성된 폴더에서 명령

ll data/kafka-1

total 32
-rw-r--r--  1 nhn  staff    0  6 21 23:18 cleaner-offset-checkpoint
-rw-r--r--  1 nhn  staff    4  6 21 23:24 log-start-offset-checkpoint
-rw-r--r--  1 nhn  staff   88  6 21 23:18 meta.properties
-rw-r--r--  1 nhn  staff   21  6 21 23:24 recovery-point-offset-checkpoint
-rw-r--r--  1 nhn  staff   21  6 21 23:24 replication-offset-checkpoint
drwxr-xr-x  7 nhn  staff  224  6 21 23:20 some-topic-2-0

 

some-topic-2 토픽의 0번 파티션에 대한 폴더가 보인다

 

ll data/kafka-1/some-topic-2-0

total 24
-rw-r--r--  1 nhn  staff  10485760  6 21 23:20 00000000000000000000.index
-rw-r--r--  1 nhn  staff        81  6 21 23:21 00000000000000000000.log
-rw-r--r--  1 nhn  staff  10485756  6 21 23:20 00000000000000000000.timeindex
-rw-r--r--  1 nhn  staff         8  6 21 23:20 leader-epoch-checkpoint
-rw-r--r--  1 nhn  staff        43  6 21 23:20 partition.metadata

 

 

cat data/kafka-1/some-topic-2-0/00000000000000000000.log

Eq���;,d��;,d��&Hello, world!%

 

hexdeump를 보여주는 xxd 명령어를 이용해보자

xxd data/kafka-1/some-topic-2-0/00000000000000000000.log

00000000: 0000 0000 0000 0000 0000 0045 0000 0000  ...........E....
00000010: 0271 b301 c700 0000 0000 0000 0001 903b  .q.............;
00000020: 2c64 8a00 0001 903b 2c64 8a00 0000 0000  ,d.....;,d......
00000030: 0003 e800 0000 0000 0000 0000 0126 0000  .............&..
00000040: 0001 1a48 656c 6c6f 2c20 776f 726c 6421  ...Hello, world!
00000050: 00

 

이를 통해 생산자가 전송한 메시지가 브로커의 로컬 디스크에 저장된다는 것을 확인할 수 있다

 

 

Offset

카프카에서는 파티셔닝을 통해 단 하나의 토픽이라도 높은 처리량을 수행할 수 있다

 

파티션의 메시지가 저장되는 위치를 오프셋이라고 부르며, 0부터 순차적으로 증가하는 64비트 정수이다

 

각 파티션에서 오프셋은 고유한 숫자로, 메시지의 순서를 보장하고 소비자가 마지막으로 읽은 위치를 알 수도 있다

 

 

고가용성 보장

카프카의 복제는 토픽이 아닌 토픽의 파티션을 복제한다

 

복제 인수에 따른 원본과 복제 수

복제 인수 Leader Follower
2 1 1
3 1 2
4 1 3

 

팔로워 수가 많다고 딱히 좋은 것은 아니다. 팔로워 수만큼 브로커의 디스크 공간도 소비된다

 

일반적으로 인수는 3을 권장한다

 

리더는 읽기 쓰기 요청을 처리하며, 팔로워는 복제만 한다

 

 

Producer

Producer Design

생산자의 전체 흐름을 이야기해보자

ProducerRecrod

필수, 선택 요소로 구성되어 있다

  • Topic
  • Partition
  • Key
    • 레코드를 정렬하기 위해 존재
  • Value

 

각 레코드들은 프로듀서의 send 메서드를 통해 다음 단계로 전달된다

Serializer

메시지를 바이트 배열로 직렬화한다

Partitioner

레코드에 파티션이 지정되어 있다면 아무 동작하지 않고 지정된 파티션으로 레코드를 전달한다

 

지정되어 있지 않다면 키를 가지고 파티션을 선택하는데, 기본적으로 Round robin 방식으로 동작한다

이후 동작

생산자가 카프카로 전송하기 전 배치 전송을 하기 위해 레코드들을 파티션별로 모아둔다

 

전송 실패 시 지정 횟수만큼 재시도 후 최종 실패를 전달한다

 

전송 성공 시 메타데이터를 반환하게 된다

Java 코드로 생산자 만들기

kafka-topics --bootstrap-server localhost:19092 --create --topic some-topic-3 --partitions 1 --replication-factor 3

Created topic some-topic-3.

 

코드는 여기서 확인할 수 있다

https://github.com/venzersiz/learn-kafka/blob/main/src/test/java/learn/kafka/producer/BasicProducerTest.java

Fire and forget

메시지 보내고 확인하지 않기

 

운영 환경에서 사용 추천하지 않음

동기 전송

메시지 전달의 성공 여부를 확인할 수 있어 신뢰성 있는 메시지 전달 가능

 

[kafka-producer-network-thread | producer-1] INFO learn.kafka.producer.AsyncProducer -- Topics: some-topic-3, Partition: 0, Offset: 0, Key: null, Message: value - 1
[kafka-producer-network-thread | producer-1] INFO learn.kafka.producer.AsyncProducer -- Topics: some-topic-3, Partition: 0, Offset: 1, Key: null, Message: value - 2
[kafka-producer-network-thread | producer-1] INFO learn.kafka.producer.AsyncProducer -- Topics: some-topic-3, Partition: 0, Offset: 2, Key: null, Message: value - 3

비동기 전송

빠른 전송이 가능하고, 메시지 전송이 실패해도 예외 처리할 수 있다

 

[kafka-producer-network-thread | producer-1] INFO learn.kafka.producer.AsyncProducer -- Topics: some-topic-3, Partition: 0, Offset: 3, Key: null, Message: value - 1
[kafka-producer-network-thread | producer-1] INFO learn.kafka.producer.AsyncProducer -- Topics: some-topic-3, Partition: 0, Offset: 4, Key: null, Message: value - 2
[kafka-producer-network-thread | producer-1] INFO learn.kafka.producer.AsyncProducer -- Topics: some-topic-3, Partition: 0, Offset: 5, Key: null, Message: value - 3

 

 

Consumer

생산자가 빠르게 카프카로 메시지를 전송하더라도 소비자가 빠르게 메시지를 읽어오지 못한다면 결국 지연이 발생한다

Consumer group

컨슈머는 반드시 그룹에 속한다

 

컨슈머 그룹은 각 파티션의 리더에게 토픽에 저장된 메시지를 가져오기 위한 요청을 보낸다. 이때 파티션 수와 하나의 컨슈머 그룹에 속한 컨슈머 수는 1:1로 매핑되는 것이 이상적이다

 

파티션 수보다 컨슈머 수가 많게 구현하는 것은 바람직하지 않다. 잔여 컨슈머는 그냥 대기 상태에 놓이기 때문이다

Rebalancing

그룹 내에서 장애 발생한 컨슈머의 역할을 그룹 내 다른 컨슈머에게 대신하게 하는 행위를 말한다

주요 옵션

group.id

컨슈머 그룹 식별자

 

enable.auto.commit

백그라운드로 주기적으로 오프셋 커밋

 

auto.offset.reset

초기 오프셋이 없거나 더 이상 존재하지 않는 경우 아래 옵션으로 Reset

  • earliest
    • 가장 초기의 오프셋 값 설정
  • latest
    • 가장 마지막 오프셋 값 설정
  • none
    • 이전 오프셋 값을 찾지못하면 에러

Java 코드로 소비자 만들기

코드는 여기서 확인할 수 있다

https://github.com/venzersiz/learn-kafka/blob/main/src/test/java/learn/kafka/consumer/BasicConsumerTest.java

오토 커밋

컨슈머 애플리케이션들의 기본값으로 가장 많이 사용됨

 

장점

  • 오프셋을 주기적으로 커밋하므로 관리자가 오프셋을 따로 관리하지 않아도 된다

단점

  • 컨슈머 종료 등이 빈번히 발생하면 일부 메시지를 못 가져오거나 중복으로 가져오는 경우가 있다

 

[Test worker] INFO learn.kafka.consumer.BasicConsumerTest -- Topics: some-topic-3, Partition: 0, Offset: 21, Key: null, Value: value - 1
[Test worker] INFO learn.kafka.consumer.BasicConsumerTest -- Topics: some-topic-3, Partition: 0, Offset: 22, Key: null, Value: value - 2
[Test worker] INFO learn.kafka.consumer.BasicConsumerTest -- Topics: some-topic-3, Partition: 0, Offset: 23, Key: null, Value: value - 3

동기 방식

속도는 느리지만, 메시지 손실은 거의 발생하지 않는다

 

메시지 손실

토픽에는 메시지가 존재하지만 잘못된 오프셋 커밋으로 인한 위치 변경으로 소비자가 메시지를 가져오지 못하는 경우

 

메세지가 손실되면 안 되는 중요한 처리 작업들은 이 방식을 권장한다. 하지만 이 방법도 메시지의 중복 이슈는 피할 수 없다

비동기 방식

오프셋 커밋을 실패하더라도 재시도하지 않다가 마지막으로 성공한 오프셋을 비동기 커밋한다

'Event streaming > Kafka' 카테고리의 다른 글

1. Kafka > 시작하기  (0) 2024.06.19