Infra/Kafka
Apache Kafka (1)
코드파고
2024. 11. 13. 17:15
Apache Kafka Series - Learn Apache Kafka for Beginners v3를 수강하며 기록한 내용입니다.
도입 이유
- 소스 시스템, 타켓 시스템이 확장됨에 따라 중재 시스템의 필요도가 높아짐
장점
- 오픈소스 프로젝트
- 좋은 성능을 보여줌
- 내결함성
- 시스템 간 결합도를 낮출 수 있다.
사용처
- 메시징
- 활동 추적
- 메트릭, 로그 수집
Topics
- 특정 데이터의 스트림
- 제약 없는 테이블과 같다
- 이름으로 토픽을 선정함
- 어느 종류의 메시지라도 괜찮다
- 연속된 메시지는 "Stream"이라 칭함
- 쿼리는 할 수 없고, Producer가 쓰면(publish), Consumer가 읽을 수 있다.
Partition
- 토픽은 파티션으로 나뉜다.
- 파티션에 데이터를 쓸 수록 Offset(index와 유사)이 증가
- 파티션에 쓰인 데이터는 불변이며, append하는 수 밖에 없다.
- 특정 기간동안만 데이터가 남아있다(디폴트 : 1주 -> 커스텀 가능)
- 오프셋은 특정 파티션에만 의미 있다.
- ex. 파티션 1의 오프셋 1 != 파티션 2의 오프셋 1
- 데이터가 지워지더라도 지워진 데이터의 오프셋은 재사용되지 않음
- 파티션은 원하는 만큼 생성 가능
- 데이터는 키가 제공되지 않으면 임의의 파티션에 선정됨
만일 위/경도를 트럭 어플리케이션이 일정 주기로 전송할 시 trucks_gps를 토픽으로 선정, 메시지를 보냄
알람 서비스, 위치 대시보드가 이 스트림을 소비할 수 있음
Offset
파티션 내에서만 순서 보장(시퀀스와 유사)
Producers
- 토픽에 데이터 적재
- 데이터를 어느 파티션에 적재할 지 알고 있음(브로커가 해당 정보를 가지고 옴)
- 브로커가 실패할 경우, 프로듀서가 자동으로 복구함Key
- 카프카 메시지의 일부
- 키가 null일 경우 라운드 로빈 방식으로 파티션에 전송
- 값이 있을 경우 특정 파티션으로 전송
Message
Serialization
- 인풋, 아웃풋 모두 바이트 형태이기 때문에 직렬화 필요
- 오브젝트 또는 데이터를 바이트로 직렬화하는 것
- 직렬화 대상 : value, key
- 메시지 키 해싱은 murmur2. 알고리즘 사용
Producer Ack
- 데이터를 쓸 때 ACK를 수신하는 것
- ack = 0 : 프로듀서가 ack를 기다리지 않음(데이터 유실 가능성 있음)
- ack = 1 : 프로듀서가 리더 ack를 기다림(데이터 유실 가능성이 일정 부분 제한됨)
- ack = all : 리더 브로커, 레플리카 브로커의 ack 모두 기다림(데이터 유실이 없음)
Consumer
- pull 모델 : 브로커로부터 데이터를 요청해서 읽는다.
- 어느 브로커로부터 읽어들여야 하는지 자동으로 알고 있다.
- 브로커가 실패하면, 복구할 방법을 안다.
- 낮은 오프셋부터 높은 오프셋 순으로 각 파티션 내에서 탐색
Deserialization
- Key 바이트를 원형으로, Value 바이트를 원형으로 역직렬화하여 사용한다.
- 토픽의 라이프사이클 동안 절대 직렬화 타입을 바꾸면 안됨 -> 바꾸려면 새로운 토픽을 만들어라
Consumer Group
- 모든 어플리케이션은 Consumer Group에 접근하여 데이터를 읽게 된다.
- 그룹 내 컨슈머는 각각 배타적으로 파티션을 읽는다.
- 파티션 개수 > 컨슈머 개수일 경우, 그룹 내 남는 컨슈머가 생기게 되며, 이는 비활성화된다.
- 하지만 서로 다른 그룹이라면 같은 파티션에 대해 붙을 수 있다.
- 컨슈머 프로퍼티 중
group.id
를 활용이전 예제에서 알람 서비스, 위치 대시보드가 `trucks_gps` 토픽을 사용하려 한다면, 알람 서비스, 위치 대시보드는 별도의 `Consumer Group`에 존재해야 한다.
- 컨슈머 프로퍼티 중
Offset
- 카프카는 어느 컨슈머 그룹이 얼만치 읽고 있었는지를 저장한다.
- 이는
__consumer_offsets
카프카 토픽에 저장. 그룹 그 자체에 저장하지 않음 - 해당 그룹이 어디까지 읽었는지 알 수 있다.
- 이는
Semantics
- 기본적으로 자바는 자동으로 오프셋을 저장하게 된다.
- 수동 조작 시 3가지 semantic이 있음
- At Least Once 최소 한번(주로 사용)
- 메시지가 사용되면 커밋됨
- 메시지 처리가 잘못되면 다시 읽을 수 있음
- 메시지를 여러번 가공할 위험이 있기 때문에 멱등성 보장이 필수적
- At Most Once 최대 한 번
- 메시지 수신 시 커밋
- 메시지 처리가 잘못 되면 메시지를 잃을 수 있음
- Exactly At Once 딱 한번
- 카프카에서 카프카로 전송할 시 Transactional API 사용(Kafka Streams API 사용)
- 카프카에서 외부 시스템으로 전송할 시: 멱등한 컨슈머를 사용해야 한다.
- At Least Once 최소 한번(주로 사용)
Brokers
- 카프카 클러스터는 다수의 브로커(서버)로 구성
- int 형태의 id로 구분됨
- 각 브로커는 특정 토픽 파티션을 포함.
- Bootstrap 브로커(사실상 아무 브로커)에 연결하면 클러스터 전체에 대해 알 수 있다.
- 3개의 브로커부터 시작하는 게 좋다. 큰 클러스터는 100개 이상의 브로커로 구성되기도 함
- 파티션을 분산하여 브로커에 분산 저장
Serach Mechanism
하나의 브로커에 일단 접속하게 되면 카프카 클라이언트는 어느 브로커에 접속할 지 찾아갈 수 있다. 순서는 다음과 같다.
- 부트스트랩 브로커 접속하여 메타데이터 요청
- 브로커는 모든 브로커 리스트를 반환
- 해당하는 브로커에 접속
Replication Factor
- 2, 3 개의(보통 3) 복제 인자를 가져야 한다
- 브로커가 다운되었을 때, 다른 살아있는 브로커의 replica 데이터를 사용한다.
- 리더 브로커를 선정해야 한다. 팔로워는 ISR(in-sync Replica)라 칭함
- 프로듀서는 리더 브로커에만 데이터 기입
- 2.7 버전 이후로 Client들은 성능 개선을 위해 가까운 브로커 replica를 읽기도 한다.
Durability
Replication Factor가 3이면, 두개의 브로커 다운까지 견딜 수 있다.
= 즉 Replication Factor가 N개이면, N-1 개의 브로커의 다운까지 견딤
Zookeeper
- 브로커를 관리
- 리더 파티션을 선정
- 변경 시 알림을 전송
- ex) 새 토픽, 브로커 다운, 브로커의 생성, 토픽 삭제, etc
- 버전별 주키퍼 사용
- 카프카 2.x는 주키퍼가 없으면 동작하지 않음
- 카프카 3.x는 Kafka Raft를 주키퍼 대신 사용
- 카프카 4.x는 Zookeeper 사용하지 않음
- 주키퍼는 홀수로 구성되어야 함
- 리더, 팔로워로 구성
- 리더 : 쓰기
- 팔로워 읽기
- v0.10 이전에는 Consumer Offset을 저장. 이후에는 topic에 저장
- 브로커를 쓰기 위해서는 Kafka 4.0 버전 출시 전 까지는 Zookeeper 사용
- 클라이언트에서는 그 동안의 버저닝을 통해 주키퍼를 대체하도록 이전해왔으므로 사용하지 않아도 됨
- 절대 Client에서 주키퍼를 쓰지 말라고 당부.. ㅎㅎ
- 카프카보다 안전하지 않기 때문에 서서히 떼어내고 있는 중
Kafka KRaft
- 카프카는 주키퍼 의존성을 떼어내고 있는 중이다.
- bec) 카프카가 10만개 이상의 파티션을 가질 시 주키퍼에서 스케일링 이슈가 발생
- 주키퍼를 떼어내면 갖는 장점
- 수백만의 파티션을 관리하고, 설정하는 데 용이
- 안정성, 모니터링, 관리가 쉬워진다.
- 주키퍼에 대한 별도 보안을 설정하지 않아도 됨(하나의 보안 모델만 관리 가능)
- 카프카를 싱글 프로세스로 시작 가능
- 컨트롤러 종료와 복구의 속도 개선
- 3.3.1 버전에서 KRaft 사용 가능
- 4.0 버전에서는 주키퍼를 완전 대체
- Quorom Controller 내에 브로커를 위치시키고 리더 브로커를 선정
- Zookeeper는 Zookeeper의 집합 레이어가 Broker 상위에서 Broker를 관리하는 형태
- Zookeeper보다 단순화된 구조를 가짐