카프카 브로커/클러스터/주키퍼
카프카 브로커는 카프카 클라이언트와 데이터를 주고받기 위해 사용되는 주체이자 데이터를 분산 저장하여 장애가 발생하더라도 안전하게 사용할 수 있도록 도와주는 애플리케이션이다.
하나의 서버에는 한 개의 카프카 브로커 프로세스가 실행된다.카프카 브로커 1대로도 기본 기능이 실행되지만
데이터를 안전하게 보관하고 처리하기 위해 3대 이상의 브로커 서버를 1개의 클러스터로 묶어서 운영한다.
카프카 클러스터로 묶인 브로커들은 프로듀서가 보낸 데이터를 안전하게 분산 저장하고 복제하는 역할을 수행한다.
데이터 저장,전송
프로듀서로부터 데이터를 전달받으면 카프카 브로커는 프로듀서가 요청한 토픽의 파티션에 데이터를 저장하고 컨슈머가 데이터를 요청하면 파티션에 저장된 데이터를 전달한다.프로듀서로부터 전달된 데이터는 파일 시스템에 저장된다.
-리눅스
ls /tmp/kafka-logs
토픽의 갯수만큼 해당 디렉토리에 파일이 생성된다.
ls /tmp/kafka-logs/mytopic-0
카프카는 메모리나 데이터베이스에 저장하지 않으며 따로 캐시메모리를 구현하여 사용하지도 않는다.
파일 시스템에 저장하기 때문에 파일 입출력으로 인해 속도 이슈가 발생하지 않을까 의문을 가질 수 있다.
일반적으로 파일 시스템은 다루기 편하지만 지속적으로 입출력할 경우 메모리에 올려서 사용하는 것보다 처리 속도가 현저히 느리기 때문이다.그러나 카프카는 페이지 캐시를 사용하여 디스크 입출력 속도를 높혀서 이 문제를 해결했다.
페이지 캐시란 os에서 파일 입출력의 성능 향상을 위해 만들어 놓은 메모리 영역을 뜻한다.한번 읽은 파일의 내용은 메모리의 페이지 캐시 영역에 저장시킨다.추후 동일한 파일의 접근이 일어나면 디스크에서 읽지않고 메모리에서 직접 읽는 방식이다.JVM 위에서 동작하는 카프카 브로커가 페이지 캐시를 사용하지 않는다면 지금과 같이 빠른동작을 기대할 수 없다.페이지 캐시를 사용하지 않으면 카프카에서 캐시를 직접 구현해야 했을 것이고,지속적으로 변경되는 데이터 때문에 가비지 컬렉션이 자주 일어나 속도가 현저히 느려질 것이기 때문이다.이러한 특징 때문에 카프카 브로커를 실행하는데 힙 메모리 사이즈를 크게 설정할 필요가 없다.
데이터 복제,싱크
데이터 복제(replication)는 카프카를 장애 허용 시스템으로 동작하도록 하는 원동력이다.
복제의 이유는 클러스터로 묶인 브로커 중 일부에 장애가 발생하더라도 데이터를 유실하지 않고 안전하게 사용하기 위함이다.
카프카의 데이터 복제는 파티션 단위로 이루어진다.토픽을 생성할 때 파티션의 복제 개수도 같이 설정되는데 직접 옵션을 선택하지 않으면 브로커에 설정된 옵션값을 따라간다.복제 개수의 최솟값은 1(복제 없음)이고 최댓값은 브로커 개수만큼 설정하여 사용할 수 있다.
복제된 파티션은 리터와 팔로워로 구성된다.프로듀서 또는 컨슈머와 직접 통신하는 파티션을 리더,
나머지 복제 데이터를 가지고 있는 파티션을 팔로워라고 부른다.통상적으로 리더와 팔로워라고 부르지만
리더 파티션,팔로워 파티션이라고 지칭한다.
팔로워 파티션들은 리더 파티션의 오프셋을 확인하여 현재 자신이 가지고 있는 오프셋과 차이가 나는 경우 리더 파티션으로부터 데이터를 가져와서 자신의 파티션에 저장하는데,이 과정을 복제라고 부른다.
파티션 복제로 인해 나머지 브로커에도 파티션의 데이터가 복제되므로 복제 개수만큼의 저장 용량이 증가한다는 단점이 있다.그러나 복제를 통해 데이터를 안전하게 사용할 수 있다는 강력한 장점 때문에 카프카를 운영할 때 2 이상의 복제 개수를 정하는 것이 중요하다.카프카 브로커가 설치된 기업용 서버는 개인용 컴퓨터와 비교가 안 될 정도로 안정성이 좋지만 서버는 해커로 인한 칩입,디스크 오류,네트워크 연결 장애 등의 이유로 언제든 장애가 발생할 수 있다.
브로커가 다운되면 해당 브로커에 있는 리더 파티션은 사용할 수 없기 때문에 팔로워 파티션 중 하나가 리더 파티션 지위를 넘겨받는다.이를 통해 데이터가 유실되지 않고 컨슈머나 프로듀서와 데이터를 주고받고록 동작할 수 있다.
운영 시에는 데이터 종류마다 다른 복제 개수를 설정하고 상황에 따라서는 토픽마다 복제 개수를 다르게 설정하여 운영하기도 한다.데이터가 일부 유실되어도 무관하고 데이터 처리 속도가 중요하다면 1 또는 2로 설정한다.
금융 정보와 같이 유실이 일어나면 안 되는 데이터의 경우 복제 개수를 3으로 설정하여 최대 2개의 브로커에서 동시에 장애가 발생하더라도 데이터를 안정적으로 유지할 수 있도록 한다.
컨트롤러
클러스터의 다수 브로커 중 한대가 컨트롤러의 역할을 한다.컨트롤러는 다른 브로커들의 상태를 체크하고 브로커가 클러스터에서 빠지는 경우 해당 브로커에 존재하는 리더 파티션을 재분배한다.카프카는 지속적으로 데이터를 처리해야 하므로 브로커의 상태가 비정상이라면 빠르게 클러스트에서 빼내는 것이 중요하다.만약 컨트롤러 역할을 하는 브로커에 장애가 생긴다면 다른 브로커가 컨트롤러 역할을 한다.
데이터 삭제
카프카는 다른 메시징 플랫폼과 다르게 컨슈머가 데이터를 가져가더라도 토픽의 데이터는 삭제되지 않는다.
또한 컨슈머나 프로듀서가 데이터 삭제를 요청할 수도 없다.오직 브로커만이 데이터를 삭제할 수 있다.데이터 삭제는 파일 단위로 이루어지는데 이 단위를 로그 세그먼트(log segment)라고 부른다.이 세그먼트에는 다수의 데이터가 들어 있기 때문에 일반적인 데이터베이스처럼 특정 데이터를 선별해서 삭제할 수 없다.세그먼트는 데이터가 쌓이는 동안 파일 시스템으로 열려있으며 카프카 브로커에 log.segment.bytes 또는 log.sement.ms 옵션에 값이 설정되면 세그먼트 파일이 닫힌다.세그먼트 파일이 닫히게 되면 기본값은 1GB 용량에 도달했을 때인데 간격을 더 줄이고 싶다면 작은 용량으로 설정하면된다.그러나 너무 작은 용량으로 설정하면 데이터들은 저장하는 동안 세그먼트 파일을 자주 여닫음으로써 부하가 발생할 수 있으므로 주의해야 한다.닫힌 세그먼트 파일은 log.segment.bytes 또는 log.retention.ms 옵션에 설정값이 넘으면 삭제된다.닫힌 세그먼트 파일을 체크하는 간격은 카프카 브로커의 옵션에 설정된 log.retention.check.inteval.ms에 따른다.
카프카는 데이터를 삭제하지 않고 메시지 키를 기준으로 오래된 데이터를 압축하는 정책을 가져갈 수도 있다.
컨슈머 오프셋 저장
컨슈머 그룹은 토픽이 특정 파티션으로부터 데이터를 가져가서 처리하고 이 파티션의 어느 레코드까지 가져갔는지 확인하기 위해 오프셋을 커밋한다.커밋한 오프셋은 _consumer_offset 토픽에 저장한다.여기에 저장된 오프셋을 토대로 컨슈머 그룹은 다음 레코드를 가져가서 처리한다.
코디네이터
클러스터의 다수 브로커 중 한대는 코디네이터의 역할을 수행한다.
코디네이터는 컨슈머 그룹의 상태를 체크하고 파티션을 컨슈머와 매칭되도록 분배하는 역할을 한다.컨슈머가 컨슈머 그룹에서 빠지면 매칭되지 않은 파티션을 정상 동작하는 컨슈머로 할당하여 끊임없이 데이터가 처리되도록 도와준다.이렇게 파티션을 컨슈머로 재할당하는 과정을 리밸런스라고 부른다.
여기까지 브로커의 역할에 대해 알아보았다.그렇다면 카프카 클러스터를 운영할 때 주키퍼가 하는 역할은 무엇일까?
주키퍼는 카프카의 메타데이터를 관리하는 데에 사용한다.주키퍼 쉘 명령어는 zookeeper-shell.sh로 실행할 수 있으며
bin 폴더 안에 있다.카프카 서버에서 직접 주키퍼에 붙으려면 카프카 서버에서 실행되고 있는 주키퍼에 연결해야 하는데
동일환경에서 접속하므로 localhost로 접속하며 포트 번호는 주키퍼 기본 포트인 2181을 입력하면 된다.
$ bin/zookeeper-shell.sh my-kafka:2181
카프카 클러스터로 묶인 브로커들은 동일한 경로의 주키퍼 경로로 선언해야 같은 카프카 브로커 묶음이 된다.
만약 클러스터를 여러 개로 운영한다면 한 개의 주키퍼에 다수의 카프카 클러스터를 연결해서 사용할 수도 있다.
토픽과 파티션
토픽은 카프카에서 데이터를 구분하기 위해 사용하는 단위이다.토픽은 1개 이상의 파티션을 소유하고 있다.
파티션에는 프로듀서가 보낸 데이터들이 들어가 저장되는데 이 데이터를 레코드(record)라고 부른다.
파티션은 카프카의 병렬처리의 핵심으로써 그룹으로 묶인 컨슈머들이 레코드를 병렬로 처리할 수 있도록 매칭된다.
컨슈머의 처리량은 한정된 상황에서 많은 레코드를 병렬로 처리하는 가장 좋은 방법은 컨슈머의 개수를 늘려 스케일 아웃하는 것이다.컨슈머 개수를 늘림과 동시에 파티션 개수도 늘리면 처리량이 증가하는 효과를 볼 수 있다.
파티션의 자료구조에서 접하는 큐()queue와 비슷한 구조라고 생각하면 쉽다.first-in-first-out(FIFO) 구조와 같이 먼저 들어간 레코드는 컨슈머가 먼저 가져가게 된다.일반적인 자료구조로 사용되는 큐는 데이터를 가져가면(pop) 레코드를 삭제하지만 카프카에서는 삭제하지 않는다.파티션의 레코드는 컨슈머가 가져가는 것과 별개로 관리된다.
이러한 특징 때문에 토픽의 레코드는 다양한 목적을 가진 여러 컨슈머 그룹들이 토픽의 데이터들 여러 번 가져 갈 수 있다.
토픽 이름 제약 조건
토픽 이름을 생성할 때 제약 조건이 있다.
- 빈 문자열 토픽 이름은 지원하지 않는다.
- 토픽 이름은 마침표 하나(.) 또는 마침표 둘(..)로 생성될 수 없다.
- 토픽 이름과 길이는 249자 미만으로 생성되어야 한다.
- 토픽 이름은 영어 대소문자와 숫자 0부터 9 그리고 마침표,언더바,하이픈 조합으로 생성할 수 있다.이외의 문자열이 포함된 토픽 이름은 생성 불가하다.
- 카프카 내부 로직 관리 목적으로 사용되는 2개 토픽(__consumer_offsets,__transction_state)과 동일한 이름으로 생성 불가능 하다.
- 카프카 내부적으로 사용하는 로직 때문에 토픽 이름에 마침표와 언더바가 동시에 들어가면 안된다.생성은 할 수 있지만 사용 시 이슈가 발생할 수 있기 때문에 마침표와 언더바가 들어간 토픽 이름을 사용하면 warning 메시지가 발생한다.
- 이미 생성된 토픽 이름의 마침표를 언더바로 바꾸거나 언더바를 마침표로 바꾼 경우 신규 토픽 이름과 동일하다면 생성할 수 없다.예를 들어 to.pic 이름의 토픽이 생성되어 있다면 to_pic 이름의 토픽을 생성할 수 없다.
####
의미있는 토픽 이름 작명 방법
토픽 이름은 데이터의 얼굴이다.토픽 이름을 모호하게 작성하면 유지보수 시 큰 어여룸을 겪을 수 있다.
예를 들어 test-20210204,abcd,bigdata,test와 같은 토픽 이름은 어떤 용도로 누가 사용하고 있는지,어떻게 만들어졌는지 알 수 없으므로 지양해야 한다.최소한 토픽 이름을 통해 어떤 개발환경에서 사용되는 것인지 판단 가능해야 하고 어떤 애플리케이션에서 어떤 데이터 타입으로 사용되는지 유추할 수 있어야 한다.만약 전사에서 공용으로 사용하고 있는 카프카라면 추가적으로 토픽의 오너십을 가진 팀의 이름을 토픽 이름에 추가하는 것도 고려해 볼 만하다.히스토리를 명확히 파악하고 싶다면 회사 내부에서 사용 중인 JIRA 티켓 번호를 토픽 이름에 넣는 것도 좋은 방법이다.
만약 카프카 클러스터를 2대 이상 운영할 경우에는 클러스터를 구분하기 위해 카프카 클러스터 이름을 넣을 수도 있다.
-토픽 작명의 템플릿과 예시
<환경>,<팀-명>,<애플리케이션-명>,<메시지-타입> prd,marking-team,sms,platform.json <프로젝트-명><서비스-명><환경><이벤트-명> commerce,payment,prd,notification <환경>,<서비스-명>,