나만의 개발 로그 | 고민 로그
카프카(Kafka) 본문
참고 자료 : https://www.conduktor.io/kafka/what-is-apache-kafka/
-> 영어의 중요성을 다시 한 번 느꼈다..
https://www.conduktor.io/kafka/what-is-apache-kafka/
www.conduktor.io
1. 왜 Kafka인가?
기존 방식의 한계
[Source A] → [Target 1]
[Source A] → [Target 2]
[Source B] → [Target 1]
…(N × M 복잡도)
- 시스템 수 늘수록 연결 수 = N × M
- 프로토콜·포맷·신뢰성 유지 지옥
Kafka 방식
[Sources] → (write) → Kafka → (read) → [Targets]
- 소스는 “쓰기”만, 타깃은 “읽기”만 → 결합도 ↓
- 브로커가 스토리지·전송 모두 담당
- 실시간 스트림·배치 분석 모두 가능
2. 주요 용어
브로커 : 카프카 클러스터 구성원, 데이터 처리하는 중심역할 하는 컴퓨터 노드, 카프카 서버 의미, 데이터 스트림 관리 및 저장하는 역할
토픽 : 클러스터 안에 있는 데이터스트림, db로 치면 테이블 느낌..
- 물론 쿼리 x → 프로듀서로 추가, 컨슈머로 리딩
- 데이터에 대한 검증이 없어서 제약없이 데이터 저장
- 클러스터 안에서 이름을 통해 식별
- 모든 종류의 메시지 형식 지원(제이슨, 바이너리..)
- 토픽 통해서 데이터스트림 만듦
파티션 : 토픽을 여러개의 파티션으로 분리 가능
- 파티션에서 메시지 순서대로 id값 생기고 증가할테고 이걸 offset이라고 함
- 카프카 토픽은 immutable함
- 데이터를 파티션에 기록하면 수정,삭제 불가
- 카프카의 데이터는 일정시간만 유지됨(기본값은 1주일)
- 오프셋은 특정 파티션에서만 의미 있다. 파티션마다 반복되기 때문
- 당연히 앞의 메시지 삭제 돼도 해당 오프셋 사용불가능! ⇒ 메시지의 순서는 한 파티션 안에서만 보장됨
- 데이터가 카프카 토픽으로 전송될 때 키가 없으면 임의의 파티션에 저장됨
즉, 각 파티션에는 메시지가 존재하고 오프셋이 증가하는 방식을 통해 순서대로 처리 가능, 다른 파티션에서는 제어불가
프로듀서 :
- 토픽에 데이터 기록
- 프로듀서는 메시지 안에 메시지 키 넣을 수 있음
- 만약 키가 없으면 데이터는 라운드로빈방식으로 전송됨
- 데이터가 파티션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
토픽에 데이터를 생성하기 위해 프로듀서가 카프카 클라이언트에 제공해야 하는 것은? 클러스터의 브로커, 토픽 이름
• 브로커 하나에만(어떤 브로커든) 연결하고, 쓰고 싶은 토픽 이름만 제공하면 된다. 카프카 클라이언트가 데이터를 적당한 브로커와 파티션으로 라우팅해준다.!
'웹 개발' 카테고리의 다른 글
View Table + JPA 매핑 (0) | 2023.11.06 |
---|---|
Schema 여정기 Multi-Tenancy(feat. PostgreSQL) (0) | 2023.11.01 |
Postman에서 AccessToken 자동 입력하기 (0) | 2023.07.23 |
Web Crawling 및 OpenSearch 사용기 (0) | 2023.07.02 |
체크 예외, 언체크 예외 (0) | 2023.05.27 |