아파치 카프카(Apache Kafka) 기초 정리

2021-10-09

데브원영님 유튜브 링크
참고 블로그

회사에서 사용하는 솔루션 기능 중에 카프카를 활용하는 부분이 있었다.
사실 솔루션 내부 기능이기 떄문에 굳이 카프카를 모르더라도 사용할 수 있고
실제 코딩을 할때도 카프카 라이브러리를 사용하지는 않지만 그래도 기초는 알고 사용하고 싶어서 조금 알아보았다.

1. Publish/Subscribe 구조

pub/sub 구조는 메세지 패턴으로 메세지를 생성하는 생산자(Publisher)와 메세지를 사용하는 수신자(Subscriber)가 분리된 구조이다. Publisher는 메세지를 생산할때 어떤 Subscriber가 사용할지 알지 못하고 Event Channel이라는 중간 관리자(?)에게 정보를 전달한다. Subscriber 역시 어떤 Publisher가 생산한 메세지인지 확인하지 않고 Event Channel에서 필요한 메세지를 가지고 오는 구조로 되어 있다. Publisher와 Subscriber가 직접적으로 연결되어 있지 않기 때문에 메시지를 생성할때 비동기 방식으로 동작할 수 있다. 또한 Publisher가 메시지를 생산할때 Subscriber를 고려하지 않아도 됨으로 느슨한 결합을 유지할 수 있다.

카프카에서는 메세지를 생산하는 producer와 메시지를 소비하는 consumer 가 있고 이 둘을 연결하는 Broker가 있는 형태로 되어 있다. 용어만 조금 다르고 구조는 비슷하다고 생각한다.

2. 카프카 브로커

카프가 브로커를 간단하게 생각하면 실행되고 있는 어플리케이션 서버 중 1개 라고 생각하면 된다. 브로커는 각각 고유의 ID를 가지고 있고 특정 topic partitionr을 가지고 있다.
카프카는 수평적인 확장(scale horizontally, scale out)을 위해 클러스터를 구성하는데 Kafka Cluste는 3개 이상의 브로커를 이용하여 구성하며 (카프카 브로커가 홀수 일 필요는 없고 3개 이상이면 된다.) 아파치 주키퍼(Apache Zookeeper)와 연동하여 메타데이터(브로커 ID, 컨트롤러 ID 등)를 저장하고 장애 상황에 대응한다고 한다.

2.1. 토픽

카프카에 전달되는 메시지 스트림의 추상화된 개념을 토픽(Topic)이라고 한다. 프로듀서는 메시지를 특정 토픽에 발행한다. 컨슈머는 특정 토픽에서 발행되는 메시지를 구독할 수 있다.

토픽의 파티션은 최소 1개 이상이 필요하고 각 파티션마다 오프셋(offset)이 붇게 된다. 메세지 처리 순서는 각 파티션이 가지고 있는 오프셋에 따라 결정된다.
컨슈머는 오프셋을 이용하여 데이터를 가지고 가는데 몇번째 오프셋까지 데이터를 읽었는지 확인하고 해당 오프셋을 기준으로 데이터를 불러온다. 오프셋은 파티션 내부에서 유일한 값을 갖는다.

파티션이 1개 일때는 큐로 데이터가 처리되지만 파티션이 여러개가 되면 완벽하게 순서를 보장하지는 않는다.

2.2. 메세지 저장(파일 시스템)

카프카 브로커는 파티션에 저장된 메세지를 파일시스템에 저장한다. 이때 만들어지는 파일을 세그먼트 파일 이라고 한다. 세그먼트 파일의 기본 용량은 1G이고 세그먼트 파일이 파일시스템은 시간, 용량 기준으로 닫히게 되고 일정시간에 따라 삭제, 압축되며 언젠가는 사라진다.

메세지를 전달 하였다고 바로 파일이 삭제되지 않기 때문에 다른 메세지 큐 방식들 보다 장애 대응에 유리하다.(장애가 발생했을때 남아 있는 세그먼트 파일에 남아있는 데이터를 확인하고 대응할 수 있다.)

2.3. 파티션 복사(replication)

카프카 브로커에 특정 파티션이 이상이 발생하였을 경우 다른 브로커에 파티션을 복제하여 복제된 데이터를 사용하는 방법으로 대응한다. 이를 replication 이라고 한다.
대부분 기본 설정으로 – replication-factor 3 으로 설정하여 고가용성을 유지한다.

리더 파티션은 kafka 클라이언트와 데이터를 주고 받는 역할을 수행한다.
팔로워 파티션은 리더파티션으로부터 레코드를 지속적으로 복제한다.(시간이 걸린다) 리더 파티션이 작동 불능 상태가 되면 나머지 팔로워 중 1개가 리더로 선출된다.

리더 파티션과 팔로우 파티션 모두가 sync 되어 있다면 ISR(in-Sync-replica)
ISR 상태가 아닌 상태에서 장애가 난다면 unclean.leader.election.enable -> 기본은 false로 설정되어 있다.(복제가 안된 상태의 파티션을 유실시키지 않고 리더가 복구할떄 까지 기다린다. 카프카 클라이언트와 상태를 비교 하여야 한다. )

3. 프로듀서

카프카 프로듀서는 토픽에 메세지를 기록한 역할을 수행한다.
프로듀서가 데이터를 작성할때 어떤 브로커와 파티션에 데이터를 작성했는지 알고 있다.
프로듀서는 메시지를 작성할 때 receive acknowledgment 를 선택할 수 있다.

구분 특징 데이터 손실 여부
acks=0 acknowledgment를 기다리지 않음 데이터 손실 가능성 있음
acks=1 leader acknowledgment를 기다림 제한된 데이터 손실 가능성
acks=all leader + ISR acknowledgment를 모두 기다림 데이터 손실 없음

프로듀서는 메세지를 전송할때 key를 같이 보낼수 있다.
key == null이면 파티션에 데이터를 저장할때 라운드로빈 으로 저장된다. 특정 key를 지정하면 해당 메세지는 항상 같은 파티션에 저장된다.

4. 컨슈머

카프카 브로커에서 데이터를 읽어오는 역할을 한다. 컨슈머는 데이터를 읽을 때 어떤 브로커와 파티션에서 데이터를 읽어왔는지 알고 있다. 컨슈머는 파티션에 데이터를 순서데로 읽어온다.

4.1. 컨슈머 그룹

컨슈머는 컨슈머 그룹을 이루어서 데이터를 읽어온다. 컨슈머 그룹 내 컨슈머는 각각 다른 파티션에 매핑되어 데이터를 읽어온다. 컨슈머 수와 파티션 숫자가 다들 경우 경우에 따라 다르게 작동한다.

파티션이 3개인 토픽에서 컨슈머가 1개일떄는 3개의 파티션으로부터 폴링이 되어 전송된다.
파티션이 3개인 토픽에서 컨슈머가 3개라면 각각의 파티션이 컨슈머에 1:1 할당 된다.
컨슈머가 4대가 되면 파티션을 할당 받지 못하도 대기하게 된다. 따라서 컨슈머는 파티션의 개수보다 같거나 작아야 한다.
만약 특정 컨슈머에 장애가 발생하면 리밸런스가 발생하는데 리밸런스 수행시 풀링이 종료 된다.
(기능이 할당 시 까지 멈춘다)

2개이상의 컨슈머 그룹을 생성하면 각각의 컨슈머 그룹을 목적에 따라 분리할수 있다.
컨슈머 그룹 A의 컨슈머가 특정 파티션에서 토픽(데이터)를 가지고 왔다고 해도 컨슈머 그룹 B의 컨슈머는 파티션에서 토픽을 가지고 올 수 있다.
(장애 대응을 위하여 컨슈머 그룹을 여러 개 지정하고 동시에 처리할 수 있다./ 각 컨슈머 그룹은 장애 간섭이 없다. -> 안정성에 도움이 준다.)

5. 기타

5.1. 카프카 스트림즈

데이터를 변환하기 위한 목적으로 사용하는 API

5.2. 카프카 커넥트

카프카 클라이언트로 데이터를 넣는 코드를 직접 작성할 수도 있지만 kafka connect를 통하여 data를 import/export 시킬 수도 있다. 코드 없이 config로 데이터를 옮기는 것이 목적으로 스트림, 배치 등으로 데이터를 전송가능

5.3. 카프카 미러 메이커

특정 카프카 클러스터 다른 카프카 클러스터로 Topic 및 record를 복제하는 것이 목적

5.4. 주의 사항

한 개의 서버랙에는 1개의 브로커만 넣어 주는 것을 추천한다. (다수의 랙에 설치해서 이슈대응을 한다.)
프로듀서, 컨슈머는 카프카 클라이언트로 자바 라이브러리로 활용 -> 카프카 브로커 버전과 클라이언트 버전 하위 호환 확인 필요
브로커와 래플리컨트의 개수 -> 래플리컨트의 개수는 브로커 개수를 초과할 수 없다. 래플리켄트가 많다고 반드시 좋은 것이 아니다.
브로커의 ID는 중복되면 안된다. 여러 브로커를 설정할 경우에는 config 설정이 필수이다.