ultra_dev
카프카(Kafka) 본문
출처 : https://www.conduktor.io/kafka/what-is-apache-kafka/
기존 : 소스시스템 - 타겟시스템 연동하려면 갯수 늘어날수록 복잡해짐
카프카 : 소스시스템에서 카프카로 보내고 타겟시스템은 카프카에서 읽어오면 됨
브로커 : 카프카 클러스터 구성원, 데이터 처리하는 중심역할 하는 컴퓨터 노드, 카프카 서버 의미, 데이터 스트림 관리 및 저장하는 역할
토픽 : 클러스터 안에 있는 데이터스트림, db로 치면 테이블 느낌..
- 물론 쿼리 x → 프로듀서로 추가, 컨슈머로 리딩
- 데이터에 대한 검증이 없어서 제약없이 데이터 저장
- 클러스터 안에서 이름을 통해 식별
- 모든 종류의 메시지 형식 지원(제이슨, 바이너리..)
- 토픽 통해서 데이터스트림 만듦
파티션 : 토픽을 여러개의 파티션으로 분리 가능
- 파티션에서 메시지 순서대로 id값 생기고 증가할테고 이걸 offset이라고 함
- 카프카 토픽은 immutable함
- 데이터를 파티션에 기록하면 수정,삭제 불가
- 카프카의 데이터는 일정시간만 유지됨(기본값은 1주일)
- 오프셋은 특정 파티션에서만 의미 있다. 파티션마다 반복되기 때문
- 당연히 앞의 메시지 삭제 돼도 해당 오프셋 사용불가능! id값 마냥 (1번 게시글 삭제해도 1번아이디가 아닌 2번 아이디부터 만들어지듯)
- ⇒ 메시지의 순서는 한 파티션 안에서만 보장됨
- 데이터가 카프카 토픽으로 전송될 때 키가 없으면 임의의 파티션에 저장됨
즉, 각 파티션에는 메시지가 존재하고 오프셋이 증가하는 방식을 통해 순서대로 처리 가능, 다른 파티션에서는 제어불가
프로듀서
- 토픽에 데이터 기록
- 프로듀서는 메시지 안에 메시지 키 넣을 수 있음
- 만약 키가 없으면 데이터는 라운드로빈방식으로 전송됨
- 데이터가 파티션0→파티션1→ 이런식으로 전송
- 동일한 키를 공유하는 모든 메시지는 해싱 전략으로 동일한 파티션에 기록!
- 만약 키를 지정할 때 특정 필드를 키로 지정하면 해당 필드 값이 같은 메시지들은 동일한 키를 가진걸로 돼서 같은 파티션에 저장된다. 이를 통해 순서 보장 가능
- 만약 게시글이 10개가 있고, username필드를 가지고 있는 경우 게시글 1,2,3은 유저네임 A가 작성, 4,5,6은 유저네임 b가 작성, 그리고 7,8,9,10은 유저네임 c가 작성한 경우, 유저네임을 키로 설정하면
- 게시글 1, 2, 3 (유저네임 A) -> 동일한 키 "A" -> 같은 파티션
- 게시글 4, 5, 6 (유저네임 B) -> 동일한 키 "B" -> 같은 파티션
- 게시글 7, 8, 9, 10 (유저네임 C) -> 동일한 키 "C" -> 같은 파티션
- 카프카 메시지 구조
- 키(메시지 키),밸류(메시지), 압축타입, 헤더(선택사항,키밸류), 어디에 전송할지 포지션+오프셋, 타임스탬프로 구성됨
- 메시지에 압축 타입 지정해서 압축도 가능
- 헤더도 추가 가능(선택사항, 키밸류 한쌍)
- 타임스탬프는 시스템 또는 유저가 설정가능
- 이렇게 생성된 카프카 메시지는 아파치 카프카로 전송돼서 저장
Kafka Message Serializer
- 프로듀서로부터 직렬화된 바이트만을 입력 받는다.
- 출력값으로 바이트를 컨슈머에게 전송한다.
- 그러나 메시지를 구성할 때는 바이트가 아니기 때문에 직렬화를 해야한다.
- 데이터나 객체를 바이트로 변환 → 이 직렬화는 값과 키에만 사용될 것
- 만약 게시글 id를 메시지 키로 설정하고 10번 게시글, 게시글 본문 “hello world”라고 가정한다면
- Key Object는 10, Value Object는 “hello world”
- ⇒ 카프카 프로듀서가 키 객체 10을 Binary 00001010 이런식으로 변환
- ⇒ 카프카 프로듀서가 값 객체 “hello world”를 Binary 00110010001010000000111010011……… 이런식으로 변환
- ⇒ 밸류 오브젝트는 ValueSerializer = StringSerializer로 지정해서 “hello world”직렬화
- ⇒ 이 경우 키 오브젝트는 KeySerializer = IntegerSerializer로 지정해서 10을 직렬화
- 이제 메시지를 아파치 카프카로 전송 가능
Consumer
:토픽에서 데이터 읽음
pull모델 구현(컨슈머가 필요한 시점에 카프카 데이터로부터 요청하고 가져오는 방식, 컨슈머가 데이터를 요청할 때까지 브로커가 데이터 보관)
컨슈머는 kafaka 브로커 즉, 서버에 데이터 요청하고 되돌아오는 응답을 받는다.
데이터를 컨슈머에게 푸싱하는건 카프카 브로커가 아니고 pull 모델이다.
컨슈머는 어떤 카프카 서버에서 데이터 읽을지 알게 된다.
브로커가 고장나도 컨슈머는 어떻게 복구할지 알게 된다.
각각의 파티션에서 데이터는 순서대로 읽히게 된다.
하지만 어떤 파티션을 먼저 읽을지는 보장되지 않는다 → 서로 다른 파티션이기 때문
카프카로부터 받은 바이트를 객체나 데이터로 변환해야됨(역직렬화)
역직렬화
- 프로듀서가 직렬화한 키, 밸류 값을 다시 역직렬화 해야함
- 바이너리 → 프로그래밍 언어가 사용할 수 있는 객체로 변환
- 컨슈머는 키와 밸류가 무슨 타입인지 알고 있어야 한다.
- 키가 int타입, value가 String타입인 걸 알고 알맞은 Deserializer로 변환 작업 ㄱㄱ
따라서 토픽의 자료형 변경하고 싶으면 새 토픽을 만들어야함
Consumer Groups
- 컨슈머그룹 안의 컨슈머 개수가 파티션 개수보다 많은 경우 잉여 컨슈머들은 대기 컨슈머가 돼서 어떤 토픽 파티션도 읽지 않는다.
- 하나의 토픽에 다수의 컨슈머 그룹이 있는 경우
- ㄱㅊ.. 하지만 컨슈머 그룹 안에서는 오직 하나의 컨슈머가 하나의 파티션에 지정될 것임
- 그럼 다수의 컨슈머그룹의 존재 이유는?
- 만약 트럭 gps정보라 치면 알림서비스나 위치기반 서비스가 동일한 데이터스트림 읽겠지
- 즉 서비스당 하나의 컨슈머 그룹을 가짐 (1번 컨슈머그룹은 알람서비스, 2번 컨슈머그룹은 위치기반 서비스)
- 카프카는 컨슈머 그룹이 읽고있던 오프셋을 저장
- 카프카 토픽 안에서 __consumer_offsets로 존재
- 컨슈머 오프셋이라는 이름의 내부 카프카 토픽에 저장한다.(엄청 옛날엔 주키퍼에 저장… 0.1버전 이전…)
- 오프셋이 커밋되면 그 컨슈머는 그 오프셋부터 계속 읽을 수 있게됨
- 아파치 아프카로부터 받은 데이터에 대한 처리를 컨슈머가 완료하면 컨슈머는 오프셋을 커밋해야 하고 카프카 브로커가 컨슈머 오프셋 토픽에 기록하라고 알림
- 오프셋을 커밋함으로써 카프카 토픽을 어디까지 성공적으로 읽었는지 카프카 브로커에게 알려줄 수 있게 된다.
- 만약 컨슈머가 죽으면 다시 돌아와서 읽었던 곳에서부터 다시 읽을 수 있게 됨
- 즉, 컨슈머 그룹 오프셋 덕분에 충돌하거나 실패한 곳부터 데이터를 재생할 수 있는 어떤 메커니즘을 가질 수 있게됨
- 카프카 토픽 안에서 __consumer_offsets로 존재
- 오프셋 방법
- At least once (주로 선호됨, 자바에서 기본값)
- 메시지 전송 후 최소한 상대방이 하나의 메시지는 받았는지 확인
- 메시지가 최소 1번 이상 전달되는 것을 보장
- 실패나 타임아웃 등이 발생하면 메시지 다시 전송 → 동일한 메시지 중복으로 처리될 수도 있다.
- 메시지가 처리된 이후 오프셋 커밋
- 처리가 잘못된 경우, 메시지 다시 읽을 수 있음
- • 프로듀서는 ACK를 받지 못하면 재전송하는 방식으로 적어도 한번 전송을 구현함.
- 중복 전송이 있을 수 있음.
- ACK = 1, all을 이용해서 구현할 수 있음.
- At most once
- 메시지 최대 1번 전송
- 메시지가 최대 한번만 소비되는 것 보장
- 메시지 받자마자 커밋하기 때문에 만약 처리가 잘못되면 일부 메시지 잃게 됨
- 메시지 실제 처리하기 전에 오프셋 커밋했으니까
- 메시지는 중복 소비되지 않을 수 있으나, 데이터 손실 가능성이 있다.
- 보내는 쪽에서 메시지 보내고 받는 사람이 받았는지 여부 확인 안함
- 장점 : 받는사람이 받았는지 확인하는 작업이 없어서 빠르고 간단
- 단점 : 처리가 잘못된 경우, 일부 메시지 손실(다시 못읽음)
- 사용처 : 일부 데이터가 누락되어도 상관없는 Log 데이터
- 프로듀서는 ACK를 기다리지 않고 다음 메세지 배치를 보낸다.
- 메세지 전송이 누락될 수 있음.
- ACK = 0으로 구현할 수 있음.
- Exactly once
- 메시지가 정확하게 1번만 전달
- 손실이나 중복 없이, 순서대로 메시지 전송하는 것은 구현 난이도가 높고 비용이 많이 든다.
- 컨슈머가 메시지 소비하고 커밋할 때, 브로커는 컨슈머가 이 메시지 처리했음을 기록한다. 만약 컨슈머에 문제가 발생해서 재시작해도, 브로커는 해당 메시지가 이미 처리된 것임을 인식하고 중복 소비 허용하지 않는다.
- At least once (주로 선호됨, 자바에서 기본값)
Kafka Brokers
- 카프카 클러스터는 다수의 브로커로 구성(브로커는 그냥 서버 의미 하는데 데이터를 받고 보내기 때문에 브로커라고 부름)
- 카프카 브로커는 id(정수)로 식별
- 각 브로커에는 특정 토픽 파티션 담김
- 즉 데이터는 모든 브로커에 분산돼서 퍼짐
- 특정 카프카 브로커에 접속하면 그 다음에 클라이언트 또는 컨슈머 또는 프로듀서는 전체 카프카 클러스터에 연결되고, 연결하는 방법을 알게됨.
- 즉, 모든 브로커가 아니라 한 브로커에 연결하는 법만 알면 나머지에도 자동으로 연결 가능
- 즉, 모든 브로커가 아니라 한 브로커에 연결하는 법만 알면 나머지에도 자동으로 연결 가능
- 카프카 브로커는 부스트랩서버라고도 불린다.
카프카 토픽 복제 계수
복제계수를 1보다 높게해서 브로커가 다운돼서 카프카 서버가 중단 됐을 때
다른 카프카 브로커에 데이터 사본이 있어서 그걸 주고 받을 수 있게 해야함
만약 파티션 2개, 복제 계수 2면 총 4개의 데이터 유닛 존재(브로커들이 다른 브로커의 데이터 복제)
클러스터 안에서 브로커 하나 죽어도 다른 브로커에 파티션들 있으니 안정적 운영 가능
- 복제를 하게 되면 파티션의 리더가 존재
- 그리고 오직 1개의 브로커만 파티션의 리더 가능
- 프로듀서는 파티션의 리더인 브로커에게만 데이터 전송 가능
- ISR = In-Sync Replication…데이터가 빠르게 복제되는.. ↔ OSR(out of sync replication)
데이터가 잘 복제되면 데이터 복제의 동기화가 일어난다.
이때 리더가 중요
리더 기본값
- 프로듀서는 파티션의 리더 브로커에게만 데이터를 전송한다.
- 컨슈머는 파티션의 리더 브로커를 통해서만 데이터를 읽는다.
- 만약 현재 리더 브로커가 다운된다면 레플리카가 리더 브로커가 돼서 프로듀서와 컨슈머에게 데이터 제공
카프카 컨슈머 레플리카 페칭(ver2.4 이후)
- 컨슈머가 가장 가까운 레플리카에서 읽게 해주는 기능
- 예를 들어서 프로듀서가 브로커1(파티션 A의 리더)한테 전송하고
- 기존에는 컨슈머가 브로커1을 통해서 읽어야 했는데
- 레플리카 페칭 기능을 쓰면 레플리카여도 가까운 브로커를 통해서 읽어도 됨
Producer Acknowledgements (acks)
프로듀서는 데이터가 성공적으로 쓰여졌다는 확인을 카프카 브로커를 통해 받을 수 있다.
3가지 세팅 존재
- acks = 0
- 프로듀서가 확인을 기다리거나 요청하지 않는다.
- 데이터가 유실될 수 있다. 브로커가 다운 돼도 모를테니까
- acks = 1
- 프로듀서가 파티션의 리더를 기다린다.
- 리더 브로커가 확인하기를 기다린다.
- acks = all
- 리더뿐 아니라 모든 레플리카가 확인하기를 기다린다.
- 데이터 유실이 없다
주키퍼
: 카프카 브로커를 관리하는 소프트웨어
- 점점 사라지는 추세..!?
- 브로커 다운될 때마다 파티션의 새로운 리더 선택 과정에 도움을 줌
- 변경 사항이 있으면 브로커들에게 알림 전송(토픽 생성 및 제거 , 브로커 다운 or 생성 등..)
- 2.x버전에서는 필수, 3.x버전부터는 주키퍼 없이도 카프카 독립적 실행(Kafaka Raft 메커니즘), 4.x버전부터는 주키퍼가 없을 것임 → 커뮤니티는 주키퍼 없이 카프카가 올바르게 작동하도록 하는 방향으로 전환 중
- 주키퍼는 홀수 개수의 서버와 함께 작동하도록 설계되어 있다.
- ex) 주키퍼 1,3,5,7개 갖게 될 것임 (일반적으로 7개를 넘지는 않음)
- 주키퍼에도 리더 존재, 나머지는 팔로워가 됨
- 즉 1개는 쓰기에 나머지는 읽기에 사용됨
- 0.1 버전 이전의 엄청 오래된 버전에서는 컨슈머 오프셋을 주키퍼에 저장하기도..
- 지금은 컨슈머 오프셋이라는 이름의 내부 카프카 토픽에 저장
주키퍼를 사용해야 하는가?
- Kafka Broker? O
- 만약 카프카 브로커 직접 관리하려면 필요하다
- Kafka Clinets? X
- 카프카 클라이언트로 할거면 필요한가? No 필요 없다.
- 모든 카프카 클라이언트와 cli툴들은 카프카 브로커를 연결 엔드포인트로 활용하도록 함
- 심지어 여기선 컨슈머도 주키퍼에 연결하면 안됨
- 2.0버전 이후로는 kafka-topics.sh 조차도 카프카 브로커를 참조한다(주키퍼가 아님!!)
- 주키퍼가 사라지는 이유
- 주키퍼는 카프카에 비해 안전하지 않다.
- 주키퍼를 사용한다면 카프카 클라이언트가 아닌 오직 카프카 브로커로부터의 연결만 수락하도록 해서 주키퍼를 보호해야됨
- 절대 카프카 클라이언트의 구성으로 주키퍼를 사용하지 마라!!
- 카프카 클라이언트: 카프카와 상호 작용하기 위해 사용되는 라이브러리
- 스프링에서 카프카 클라이언트를 사용하는 경우, 스프링 카프카 프로젝트를 사용하거나 KafkaTemplate과 같은 스프링 카프카 지원 클래스를 사용하여 메시지를 생성하고 소비할 수 있다
Kafka Kraft
- 주키퍼 사용하지 않는 이유
- 파티션이 100,000개 넘어가면 카프카 클러스터에서 스케일링 문제 발생하기 때문!!
- 시스템 전체에 하나의 보안 모델 적용 가능(주키퍼 보안 신경 안쓰고 카프카 보안만 신경 쓰면되니까)
Quiz
토픽에 데이터를 생성하기 위해 프로듀서가 카프카 클라이언트에 제공해야 하는 것은? • 클러스터의 브로커, 토픽 이름
• 브로커 하나에만(어떤 브로커든) 연결하고, 쓰고 싶은 토픽 이름만 제공하면 된다. 카프카 클라이언트가 데이터를 적당한 브로커와 파티션으로 라우팅해준다.!
'TIL&WIL' 카테고리의 다른 글
Elastic Search License 관련 (0) | 2023.11.13 |
---|---|
지난 1년 간의 회고, 학습 내용 총 정리 (1) | 2023.10.25 |
포스트맨 토큰 자동 입력 방식(spring, nest js) (0) | 2023.07.23 |
Web Crawling 및 OpenSearch 사용기 (0) | 2023.07.02 |
하이퍼 v (0) | 2023.05.25 |