You will be fine

<Kafka> 5. 카프카 내부 메커니즘 살펴보기

by BFine
반응형

출처&참고 : https://product.kyobobook.co.kr/detail/S000201464167

 

카프카 핵심 가이드 | 그웬 샤피라 - 교보문고

카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로

product.kyobobook.co.kr

 

가. 컨트롤러 

 a. Ephemeral Node

  -  카프카는 현재 클러스터의 멤버인 브로커들의 목록을 유지하기 위해 아파치 주키퍼를 사용한다.

      => 각 브로커는 브로커 설정파일에 저장되어있거나 아니면 자동으로 생성된 고유한 식별자를 가진다.  

  -  브로커 프로세스는 시작될때마다 주키퍼에 Ephemeral Node 형태로 ID를 등록하며(동일ID는 실패) 이게 추가되거나 제거될때마다 알람을 받는다. 

      => Ephemeral Node 는 일시적 데이터나 단기적으로 유지되어야하는 연결을 저장하기 위해 생성되는 요소이다.

 

 b. 주키퍼 기반 카프카 프로세스

  -  클러스터에서 가장 먼저 시작되는 브로커는 주키퍼의 /controller에 Ephemeral Node를 생성함으로써 컨트롤러가 된다.

      => 다른 브로커들은 이미 생성되어있다는 응답을 받기 때문에 클러스터안에 단 한 개의 컨트롤러만 있도록 보장 할 수 있다.

  -  컨트롤러는 일반적인 카프카 브로커의 기능에 더해서 파티션 리더를 선출하는 역할을 추가적으로 맡는다.

  -  컨트롤러 노드에 변동이 있을수 있기 때문에 남은 브로커들을 알림을 받기 위해 와치 설정을 한다.

      => 새로운 컨트롤러가 선출되면 에포크 혹은 세대 값을 브로커들에게 전달하게 되고 이후에 더 낮은 에포크 값을 가진 메세지는 무시한다.

  -  컨트롤러가 브로커가 나갔다는 사실을 알게되면 해당 브로커가 맡고 있던 모든 파티션에 대해 새로운 브로커를 할당한다.

      => 새로운 리더 브로커가 필요한 모든 파티션을 순회하면서 리더가 될 브로커를 결정한다.

 

 c. 주키퍼 방식의 문제점  

  -  주키퍼가 맡고 있는 중요한 2가지 기능은 컨트롤러 선출, 클러스터 메타데이터 (브로커, 설정, 토픽, 파티션, 레플리카 관련 정보) 저장이 있다. 

  -  문제점

      1. 컨트롤러가 주키퍼에 메타데이터 쓰는 작업은 동기, 브로커에 메세지를 보내는 작업은 비동기로 메타데이터 불일치가 발생할 수 있다.

      2. 컨트롤러가 재시작 될때마다 주키퍼로부터 모든 브로커와 파티션에 대한 메타데이터를 읽어와야한다. 

          => 파티션과 브로커수가 증가 할수록 재시작이 느려진다.

      3. 메타데이터 소유권 관련 내부 아키텍처에서 어떤 작업은 컨트롤러, 다른 것은 브로커, 그 외 주키퍼 이런식으로 통합되지 못하고 분산되어있다.

 

 d. KRaft(Kafka Raft) 기반 카프카 프로세스

  -  주키퍼 기반 컨트롤러로부터 탈피해 3.3 부터 정식으로 프로덕션 환경에 사용 가능한 기능이 되었다.

  -  KRaft 핵심 아이디어는 카프카 그 자체에 사용자가 상태를 이벤트 스트림으로 나타낼수 있도록 하는 로그 기반 아키텍처를 도입한다는 점이다.

  -  즉 주키퍼 대신 카프카 로그에 모든 클러스터 상태와 메타데이터가 이벤트 형태로 순차적으로 기록되고 이를 사용해서 클러스터의 상태를 관리한다.

      => 카프카가 주키퍼 없이 독립적으로 메타데이터 관리, 컨트롤러와 브로커들이 직접 카프카 로그에서 메타데이터를 가져와서 일관성을 유지한다.

  -  이 새로운 아키텍처에서 컨트롤러 노드들은 메타데이터 이벤트 로그를 관리하는 래프트 쿼럼(Raft Quorum)이 된다.

      => Ratf는 합의를 유지하는 알고리즘으로 여러 노드들이 같은 상태를 가지도록 보장하는 알고리즘이다.

      => Quorum은 분산 시스템에서 합의를 얻기 위한 최소한의 노드 수를 의미한다. (Raft Quorum은 합의의 주체를 의미한다. 여기선 컨트롤러 노드들)

  -  컨트롤러 노드들은 외부시스템에 의존하지 않고 자체적으로 리더를 선출할 수 있게 되었다.

      => 메타데이터 로그의 리더 역할을 맡고 있는 컨트롤러는 액티브 컨트롤러 라고 부른다.

  -  액티브 컨트롤러는 브로커가 보내온 모든 RPC 호출을 처리하고 팔로워 컨트롤러들은 액티브 컨트롤러의 데이터를 복제한다.

      => 액티브 컨트롤러에 장애가 발생해도 컨트롤러들이 모두 최신 상태를 가지고 있어 바로 대체가 가능하다 (새 컨트롤러로 이전하는 리로드 기간 불필요)

구분 주키퍼 기반 KRaft 기반
컨트롤러 수 1개 1 + N개 (액티브 컨트롤러 + 팔로워 컨트롤러들)
리더 선출 주키퍼가 관리 Raft 프로토콜을 통해 컨트롤러들이 자체선출
메타데이터 관리 주키퍼에 저장 kafka 내부 이벤트 스트림 형태 로그로 저장
고가용성 새로운 리더 선출 팔로워 중 한명이 리더로 전환

  

나. 복제

 a. 카프카 아키텍처의 핵심

  -  복제는 카프카 아키텍처의 핵심으로 개별적인 노드에 필연적으로 장애가 발생할 수 밖에 없는 상황에서 카프카가 신뢰성과 지속성을 보장하는 방식이다.

  -  카프카에 저장되는 데이터는 토픽을 단위로해서 조직화되고 각 토픽은 1개 이상의 파티션으로 분할되며 각 파티션은 다수의 레플리카를 가질 수 있다.

      => 각각의 레플리카는 브로커에 지정되는데 하나의 브로커는 수백개에서 수천 개의 레플리카를 저장할 수 있다.

 

 b. 종류

  -  리더 레플리카 : 일관성을 보장하기 위해 모든 쓰기요청을 처리한다. 또한 팔로워 레플리카들이 최신 상태를 유지하고 있는지도 체크한다.

  -  팔로워 레플리카 : 리더 레플리카를 제외한 나머지를 팔로워 레플리카라고 하며 리더 레플리카로 들어온 메세지를 복제하여 최신상태를 유지한다.

 

다. 요청처리 

 a. 카프카 내부

  -  브로커는 억셉터 스레드를 통해 연결을 생성하고 들어온 요청들을 받아서 요청 큐에 넣고 응답 큐에서 응답을 가져다 클라이언트로 보낸다

  -  요청이 큐에 들어오면 I/O 스레드가 요청을 가져와서 처리하는 일을 담당한다. 요청 유형은 쓰기 요청, 읽기 요청, 어드민 요청 3가지가 있다

      => 위의 purgatory는 임시 보관 역할을 하는 저장소로 주로 컨슈머 지연이나 요청 대기상태를 관리하기 위해 사용된다.

 

 b. 쓰기요청

  -  파티션의 리더 레플리카가 가지고 있는 브로커가 해당 파티션에 쓰기요청을 받으면 먼저 유효성 검증을 진행한다.

      => 프로듀서가 토픽에 대해 쓰기권한을 가지고 있는지, acks 설정값이 유효한지, all인 경우 메세지를 쓸수 있을 만큼 인-싱크 레플리카가 충분한지 

  -  유효성 검증이 끝나면 브로커는 메세지를 로컬디스크에 쓰는데 리눅스의 경우 메세지는 파일시스템 캐시에 쓰여진다.

      => 이때 언제 디스크에 반영될지에 대한 보장은 없는데 그 이유는 카프카는 이 저장될때까지 기다리지 않는다. (메세지 지속성을 복제에 의존!)

  

 c. 읽기요청 

  -  클라이언트는 읽기 요청을 정확히 라우팅 할 수 있도록 필요한 메타데이터에 대한 요청을 보내게된다.  

  -  요청을 받는 파티션 리더는 먼저 요청이 유효한지를 확인한다.(오프셋이 파티션에 존재하는지 등)

  -  카프카는 클라이언트에 보내는 메세지에 제로카피(zero-copy) 최적화를 적용해 파일에서 읽은 메세지들을 중간버퍼 없이 바로 네트워크 채널로 전송한다.

      => 데이터를 복사하고 메모리 상에 버퍼를 관리하기 위한 오버헤드를 줄여 성능을 향상 시켰다.

 

라. 물리적 저장소

 a. 카프카의 저장

  -  카프카의 기본 저장 단위는 파티션 레플리카이며 파티션은 브로커들 사이에서 분리될수 없고 브로커 내의 서로 다른 디스크에 분할 저장 할수도 없다.

  -  3버전부터 계층화된 저장소 기능이 추가 되었는데 클러스터 저장소를 로컬과 원격 두 계층으로 나누어 저장하는 것이다.

      => 로컬계층은 로컬 디스크를 사용하는것이고 원격 계층은 완료된 로그세그먼트를 저장하기 위해 HDFS나 S3 같은 시스템을 사용한다. 

 

 b. 인덱스

  -  컨슈머가 임의의 오프셋에서 메세지를 읽어오기를 시작할 수 있기 때문에 카프카는 타임스탬프와 메세지 오프셋을 매핑하는 인덱스를 가지고 있다. 

      => 이 인덱스는 타임스탬프를 기준으로 메세지를 찾을때 사용되며 인덱스 역시 세그먼트 단위로 분할된다.

  -  체크섬을 유지하지 않기 때문에 오염될 경우 해당하는 로그 세그먼트에 포함된 메세지들을 다시 읽어서 오프셋 위치를 기록하는 방식으로 재생성된다.

 

 c. 압착

  -  카프카는 설정된 기간 동안만 메세지를 저장하며 보존시간이 지나간 메세지들은 삭제한다.

  -  압착 보존 정책은 토픽에서 각 키의 가장 최근 값만 저장하도록 한다. (log.cleaner.enabled 설정)

  -  압착 구조는 클린영역과 더티영역으로 나뉘는데 클린 영역은 하나의 키마다 하나의 값만 포함한다. (압착시점에서 가장 최신값을 의미한다)

  -  액티브 세그먼트를 압착하는 것이 아닌 세그먼트에 저장되어있는 메세지만 압착이 대상이 된다. 

 

마. 신뢰성 

 a. 카프카는 무엇을 보장하는가!

  -  파티션 안의 메세지들 간에 순서를 보장한다.

  -  클라이언트가 쓴 메세지는 모든 인-싱크 레플리카의 파티션에 쓰여진 뒤에야 커밋된 것으로 간주된다.

      => 컨슈머는 커밋된 메세지만 읽을 수 있다.

  -  커밋된 메세지들은 최소 1개의 작동 가능한 레플리카가 남아 있는 한 유실되지 않는다.

 

 b. 복제

  -  인-싱크 레플리카 상태 조건

      1. 주키퍼와의 활성 세션이 있어야 한다. (설정시간[기본값 6s] 사이에 주키퍼로 하트비트를 전송 성공)  

      2. 설정시간[기본값 10s] 사이에 리더로부터 메세지를 읽어야한다.

      3. 설정시간[기본값 10s] 사이에 리더로부터 읽어온 메세지들이 가장 최근 메세지이어야한다.

  -  업데이트 내역을 따라오지 못하거나 주키퍼와 연결이 끊어진 경우 레플리카는 아웃-오브-싱크 상태가 된다.

  -  복제 팩터 설정으로는 토픽 단위는 replication.factor, 브로커 단위는 default.replication.factor에 설정 할 수 있다.

      => 설정값은 개수단위 이며 N개이면 N-1개의 브로커가 중단되더라도 토픽의 데이터를 읽거나 쓸 수 있다.

 

 c. 언클린 리더 선출

  -  파티션 리더가 유효하지 않을 경우 인-싱크 레플리카 중 하나가 새 리더가 된다. 

      => 커밋된 데이터에 아무런 유실이 없음을 보장한다는 점에서 클린이라고 부른다. 

  -  그러나 인-싱크 레플리카가 없는 경우에는 복구될때까지 기다려야하는 문제가 발생한다.

      => 이때 아웃-오브-싱크 레플리카를 리더로 선출할 수 있는 설정값이 있는데 unclean.leader.eletion.enable이다. (기본값은 false)

  -  아웃-오브-싱크 레플리카를 허용할 경우 데이터 유실과 일관성 깨짐의 위험성이 있기 때문에 주의해야한다.

 

 d. 신뢰성 있는 처리를 위한 컨슈머 설정 

  -  신뢰성 수준을 결정하기 위해서 알아두어야 하는 속성값으로는 4개가 있다.

    1. group.id 같은 그룹아이디를 가진 두개의 컨슈머가 있을때 각 컨슈머들은 서로 다른 부분의 메세지만 읽게 된다.

    2. auto.offset.reset 은 유효한 오프셋이 없을때에 대한 설정으로 earliest는 파티션 맨 앞에서부터, lastest는 파티션 끝에서부터 읽는다.

    3. enable.auto.commit 은 컨슈머가 오프셋 커밋을 자동으로 관리할지 아니면 코드에서 직접 커밋할지에 대한 설정이다.

        => 자동 오프셋 커밋의 단점은 메세지 중복처리를 제어할수없다는 점이 있다. 

        => 비동기로 처리를 수행하기 위해 다른 스레드에 레코드를 넘기는것과 같이 복잡한 처리를 해야할 경우에는 수동으로 관리할수 밖에 없다.

    4. auto.commit.interval.ms 는 3번과 연관된 것으로 자동으로 커밋되는 주기를 설정 할 수 있다.

  -  마지막으로 읽어온 오프셋을 커밋하는 것이 아니라 언제나 처리가 완료된 메세지의 오프셋을 커밋하는게 중요하다.

  -  #30 실패, #31 성공일때 #31 오프셋을 커밋하게 되면 #30까지도 성공한것으로 처리가 된다. 재시도 가능한 에러인 경우 아래 두가지 방법이 있다.

      1.  나중에 처리해야할 레코드들을 버퍼에 저장하고 컨슈머의 pause() 메서드를 호출해서 추가적인 poll() 하지않고 레코드를 처리

      2. 오류난 레코드들을 별도 토픽에 쓴 뒤 별도의 컨슈머 그룹을 사용하거나 모든 토픽을 구독하는 컨슈머를 두고 재시도 사이에 구독을 잠시 멈추고 처리 

          => 메세지 교환 시스템에서 사용되어 온 데드레터큐(Dead Letter Queue)시스템과 비슷하다. (카프카에서는 Dead Letter Topic, DLT) 

  -  컨슈머 주요 지표로는 컨슈머 랙(lag) 이 있는데 브로커 내 파티션에 커밋된 가장 최신 메세지에서 얼마나 뒤떨어져있는지를 가르킨다 

 

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기