카프카의 특징
- Pub/Sub 동작 방식이다.
- Publisher와 Subscriber가 독립적으로 데이터를 생산하고 소비한다. 프로듀서와 컴포넌트를 디커플링하기 위한 좋은 수단이다.
- 이런 느슨한 결합을 통해 둘 중 하나가 죽어도 서로간에 의존성이 없으므로 안정적으로 데이터를 처리할 수 있다.
- 리시버와 컬렉터의 통신을 API로 한다고 가정해보자.
- 컬렉터는 리시버가 요청한 것을 바로 처리해줘야 한다. (처리를 미룰 수 없다)
- 실패가 났을 때 부분 재처리가 불가능하다.
- 처리속도가 느린 컴포넌트 기준으로 처리량이 결정된다. 그러므로 트래픽이 몰리는 상황을 대비해 전체 파이프라인을 넉넉하게 산정해야 한다.
- 중간에 카프카를 두게 되면 이런게 좋아진다.
- 컬렉터는 리시버가 요청한 작업을 쌓아두고 나중에 처리할 수 있다.
- 실패가 나도 컬렉터만 따로 재처리 할 수 있다.
- 일시적으로 트래픽이 몰리는 상황을 대비해서 제일 프론트의 서버만 넉넉하게 준비해두면 된다. 물론 장기적이 되면 전체 파이프라인을 증설해야 겠지만.
- 리시버와 컬렉터의 통신을 API로 한다고 가정해보자.
- 또한 설정 역시 간단해진다.
- Publisher 따로, Consume 따로 독립적으로 설정할 수 있다.
- 디스크 순차 저장 및 처리
- 메시지를 메모리큐에 적재하면 데이터 손실 가능성이 있지만 카프카는 디스크에 쓰므로 데이터 손실 걱정이 없다.
- 디스크에 쓰므로 상대적으로 속도가 느리지만, 순차처리 방식으로 디스크 I/O를 줄여 그렇게 느리지도 않다. 엄청 반응성이 높은 서비스가 아니라면 이 속도가 문제되진 않는다.
카프카 아키텍처 구성요소
- 토픽
- 카프카 안에는 여러 레코드 스트림이 있을 수 있고 각 스트림을 토픽이라고 부른다.
- 하나의 토픽에 대해 여러 Subscriber가 붙을 수 있다.
- 파티션
- 각 토픽마다 데이터를 여러개의 파티션에 나누어서 저장/처리한다.
- 토픽 사이즈가 커질 경우 파티션을 늘려서 스케일아웃을 할 수 있다.
- 일반적으로 브로커 하나당 파티션 하나다.
- 컨슈머 인스턴스 수는 파티션 갯수를 넘을 수 없다. 그러므로 병렬처리의 수준은 파티션 수에 의해 결정된다. 즉 파티션이 많을 수록 병렬처리 정도가 높아진다.
- 각 파티션마다 Publish되는 레코드에 고유 오프셋을 부여한다. 때문에 레코드는 파티션 내에서는 유니크하게 식별된다. 하지만 파티션간에는 순서를 보장하지 않는다.
- 전체 순서를 보장하고 싶으면 파티션을 하나만 두는 수 밖에 없는데, 이러면 데이터량이 많아져도 스케일아웃이 안되고, 컨슈머 인스턴스도 하나만 둘 수 있으므로 병렬 컨슈밍도 안된다.
- 데이터 보관기간
- 컨슘하고는 상관없다. 보관기간(Retention) 정책에 따른다.
- 데이터 사이즈가 늘어날지라도 성능은 일정하게 유지된다.
- 오프셋
- 일반적으로 컨슈머가 오프셋을 순차적으로 증가하며 컨슘해하지만, 원한다면 컨슈머 마음대로 조정할 수 있다. 재처리가 필요한 경우 오프셋을 이전으로 돌릴 수도 있고 가장 최근 레코드 부터 처리할 수도 있다.
- 컨슈밍 비용이 저렴하기 때문에 커맨드라인 컨슈머로 데이터를 "tail"하는 작업도 다른 컨슈머에 별 영향을 끼치지 않는다.
- 프로듀서
- 레코드를 프로듀스할 때 어느 토픽의 어느 파티션에 할당할 지를 결정한다.
- 일반적으로 라운드로빈 혹은 원하는 대로 할당방식을 지정할 수 도 있다.
- 컨슈머 그룹
- 컨슈머 그룹 마다 독립적인 컨슘 오프셋을 가진다.
- 컨슈머 그룹 내에서 처리해야할 파티션이 분배된다. 즉 하나의 파티션은 하나의 서버가 처리한다. 그룹에 서버가 추가되면 카프카 프로토콜에 의해 동적으로 파티션이 재분배 된다.
- 하나의 토픽 레코드를 분산처리하는 구조라면 동일 컨슈머 그룹을 가지게 해야 한다.
- 하나의 토픽 레코드에 각각 별도의 처리를 하는 다른 파이프라인이라면 서로 다른 컨슈머 그룹을 가지게 해야 한다.
- 큐(Queue)와 Pub/Sub의 혼합 모델
- 컨슈머그룹 내에서는 큐처럼 동작하고 컨슈머그룹간에는 Pub/Sub처럼 동작한다. 컨슈머그룹을 어떻게 두냐에 따라 두 방식 다 입맛에 맞게 선택할 수 있다.
- 데이터 순서 보장
- 일반적인 큐 모델에서 순차적으로 컨슘해간다고 해도 각 처리기에 도달하는 시간이 다르므로 순서가 보장되지 않는다.
- 그렇다고 처리기를 1개만 두면 병렬성이 떨어진다.
- 카프카는 토픽의 전체 순서는 보장하지 않지만 파티션별로는 처리 순서를 보장한다. (파티션은 하나의 컨슈머 인스턴스에서만 처리되므로). 즉 전체 순서 보장을 포기하고 부분 순서 보장을 취한 것이다. 대신 병렬성을 유지할 수 있다. 다르게 말하면 파티션당 컨슈머를 하나만 가져야하는 이유는 파티션 내에서 처리순서를 보장하기 위함이다.
데이터 복제
- 각 파티션을 여러대의 서버에 복제해둔다. 이는 설정값을 따른다. 크래시리포트는 ?개의 리플케이션을 두었다.
- 각 파티션 마다 특정 서버 한대를 리더로 선정하고 나머지 복제 파티션은 팔로워가 된다.
- 리더가 모든 읽기/쓰기를 처리한다. 리더는 요청을 패시브하게 팔로워들에게 전파한다.
- 각 서버는 하나의 리더 파티션을 갖는다. 때문에 트래픽은 각 서버로 밸런싱이 되는 셈이다.
- 복제 수가 N개라면 N-1개까지 죽어도 실패복구가 가능하다.
주키퍼의 역할
- 컨트롤러를 선출한다. 컨트롤러는 전체 파티션에 대한 리더/팔로워를 관리한다. 컨트롤러의 역할은 노드가 죽으면 다른 리플리카에게 리더가 되라고 명령을 내리는 역할을 한다.
- 어떤 브로커가 살아있는지를 체크한다.
- 어떤 토픽이 있고, 토픽에 파티션이 몇개 있고, 리플리카는 어디있고, 누가 리더가 될만한지, 각 토픽에 어떤 설정이 되어있는지를 관리한다.
오프셋 관리
- 카프카에서 관리하는 오프셋은 두 종류가 있다.
- Current Offset : 컨슈머에게 전송된 마지막 레코드의 포인터
- Committed Offset : 컨슈머가 성공적으로 처리한 마지막 레코드의 포인터.
- 오프셋 커밋 방법
- 카프카에서 관리되는 방식 : _consumer_offsets 토픽에 오프셋이 저장된다.
- 오토 커밋 : 일정 주기를 가지고 자동으로 커밋한다. 작업이 끝났든 안끝났든 주기만 되면 커밋한다.
- 커밋되기 전에 리밸런싱이되면 중복처리가 발생한다.
- 처리 완료되기 전에 커밋 후 프로세스가 죽으면 데이터 손실이 발생한다. (at-least-once 보장X)
- 수동 커밋 : 수동으로 커밋 API를 호출한다. 정확히 작업이 끝나고 커밋하는 걸 보증하기 위해 사용한다.
- 커밋되기 전에 리밸런싱되면 중복처리가 발생한다.
- 오토 커밋 : 일정 주기를 가지고 자동으로 커밋한다. 작업이 끝났든 안끝났든 주기만 되면 커밋한다.
- 자체적으로 관리하는 방식 : 별도 DB등에 자체적으로 오프셋을 관리한다.
- 카프카에서 관리되는 방식 : _consumer_offsets 토픽에 오프셋이 저장된다.
- 메시지 보증 전략
- 일반적인 보증 전략은 다음과 같고 카프카는 at-least-once로 보증한다.
- at-most-once : 중복X, 유실O
- exactly-once : 중복X, 유실X, 구현하기 어렵다.(비용이 비싸다)
- at-least-once : 중복O, 유실X
- 데이터가 중복되는 경우는 다음과 같다. 어떤 데이터가 poll됐으나 commit되지 않은 시점에 리밸런싱 작업이 일어나면 이미 poll되어서 처리중인 데이터를 다른 컨슈머가 또 poll갈 수 있다.
- 수동 커밋에서도 마찬가지다. 커밋 전에 리밸런싱이 되는 경우 중복으로 처리될 수 있다.
- 가장 좋은건 exactly-once지만 비용이 비싸므로 적당한 타협지점은 at-least-once이다.중복되는 메시지는 메시지 ID나 시간등으로 어느정도 보정이 가능하기 때문이다.
- 카프카 0.11 버전 부터 idempotent producer와 tracsaction producer의 등장으로 exactly-once를 보장할 수 있다고 한다.
- 내부적으론 레코드의유니크ID, 트랜잭션ID, 프로듀서ID등을 조합해서 처리한다고 한다.
- 일반적인 보증 전략은 다음과 같고 카프카는 at-least-once로 보증한다.
- 크래시리포트의 오프셋 관리
- 스파크 클러스터만 수동커밋을 하고 있다. 스파크는 보통 poll에서 프로세싱 완료까지 시간이 오래걸릴 것으로 예상되므로 오토커밋을 할 경우 데이터 손실 가능성이 커서 수동으로 커밋하는 걸로 보인다.
- 오프셋을 카프카가 아닌 별도의 DB에 저장했다. commitAsync()에서 fail이 발생했던 거 같은데 정확한 원인은 모르겠다.
다른 미들웨어와 비교해보자.
- RabbitMQ
- 장점
- 다양한 기능, 높은 성숙도
- 20k/sec 처리 보장
- 장점
- Kafka
- 장점
- 고성능 고가용성
- 분산처리에 효과적으로 설계됨
- 100k/sec 처리 보장
- 장점
겪었던 이슈들
ERROR [Thread-20] [Consumer clientId=consumer-13, groupId=xxx-collector] Offset commit failed on partition zxx.topic-1 at offset 199: The request timed out.
원인은 글쎄.... 뭐였지...
'개발 > 기타' 카테고리의 다른 글
SAML / OAuth2.0 / OpenIDConnect (1) | 2020.05.12 |
---|---|
[PHP] Call to undefined function dl() (0) | 2020.02.10 |
Cross-origin 파일 이름 변경해서 다운로드 하기 (0) | 2020.01.09 |
Vue.js에서 Props가 안넘어가는 문제 (undefined) (0) | 2020.01.08 |
[Java] 원격 디버깅(Remote debugging) (0) | 2019.12.18 |