KafkaJS

본 글은 네이버 부스트캠프 과정을 위해 별도 학습 후 작성한 노션 문서를 이전한 글입니다.


배경지식

MOM (Message Oriented Middleware) : 메시지 지향 미들웨어

  • 미들웨어 : 어플리케이션들을 연결시켜, 서로 데이터를 교환할 수 있게 해주는 소프트웨어
  • 메시지 지향 : 메시지 전달을 통해 데이터를 교환할 수 있게 만들어주는 시스템

Message Broker

Untitled

  • MOM 기반으로 메시지 전송을 담당하는 시스템
  • Publisher(송신자)로부터 전달받은 메시지를 Subscriber(수신자)로 전달해주는 중간 역할
    • 응용 소프트웨어 간에 메시지 교환을 담당
  • Message Queue : 메시지가 저장되는 공간
  • 종류 : RabbitMQ, Apache Kafka, ActiveMQ
  • 장점
    • 비동기 : Queue에 넣기 때문에 후처리 가능
    • 비동조 : 애플리케이션과 분리 가능
    • 탄력성 : 일부가 실패해도, 전체에 영향 미비
    • 과잉 : 실패할 경우 재실행 가능
    • 보증 : 작업완료 확인 가능
    • 확장성 : 다수의 프로세스들이 큐에 메시지를 보낼 수 있음

소개

카프카란

Kafka(통칭 카프카)는 시스템 간에 데이터를 안전하게 이동하는 메시징 시스템이다. 각 구성 요소의 구성 방식에 따라 실시간 이벤트 추적을 위한 전송 또는 복제된 분산 데이터베이스로 작동할 수 있다. 일반적으로 큐라고 부르지만 큐와 데이터베이스 사이에 있으며 두 시스템 유형의 특성과 장단점을 가지고 있다고 볼 수 있다.

링크드인에서 시작되었으나 현재는 아파치 공식 오픈소스이다.

Pub/Sub 모델

image

  • 중앙에 메시징 시스템 서버를 두고, 메시지를 보내고(publish), 받는(subscribe) 형태의 통신
  • 구독을 신청한 수신자만 메시지를 전달받을 수 있음
  • 장점
    • 기존 네트워크 통신 방식의 단점 극복
    • 개체가 수신 불능이 되도 메시징 시스템만 살아있으면 전달한 메시지가 유실되지 않음
    • N:M으로 연결되는 것이 아니기 때문에 확장성이 용이
  • 단점
    • 직접 통신하는 것이 아니기 때문에 메시지가 정확하게 전달되었는지 확인하려면 코드가 복잡해짐
    • 메시징 시스템이 있기 때문에 메시지 전달속도가 느림
  • 일반적인 형태의 네트워크 통신 image
    • 장점 : 속도가 빠름
    • 단점 : 송,수신자 상태에 따라 장애 처리를 해줘야 함, 참여하는 개체가 많아질 수록 각 개체를 연결해줘야 함(확장성에서 불리)

카프카 도입 이전

기존 end-to-end 연결 방식 아키텍처의 많은 문제점 해결을 위해서

  1. 통합/중앙화된 전송 영역이 없음 → end-to-end 연결이 갈수록 복잡해짐
  2. 문제 발생 시 관련 여러 시스템을 확인해야 함 → 문제 해결이 어려워짐, 하드웨어 증설과 같은 작업이 어려움
  3. 데이터 파이프라인 관리의 어려움
  4. 연결된 시스템마다 제각기 다른 방식으로 구현될 수 있음 → 파이프라인 통합(확장)이 어려움

카프카 도입으로 해결되는 것

  1. 모든 시스템으로 데이터를 전송할 수 있고
  2. 실시간 처리가 가능하고
  3. 급속도로 성장하는 서비스를 위해 확장이 용이

카프카가 도입되기 전 링크드인의 데이터 처리 시스템

image

카프카를 이용한 링크드인의 데이터 처리 방식 (이벤트/데이터 흐름을 중앙에서 관리)

image

용어

  • Cluster : 카프카가 실행중인 집합적인 머신 그룹
  • Broker : 단일 카프카 인스턴스
  • Topic : 데이터를 구성하는데 사용된다. 항상 특정 Topic을 읽고 쓴다.
  • Partition : 토픽의 데이터는 여러 파티션에 분산되어 있는데, 각 파티션은 시간순으로 정렬된 로그 파일로 생각할 수 있다. 올바른 순서로 메시지를 읽을 수 있도록 컨슈머 그룹의 한 구성원만 한 번에 특정 파티션에서 읽을 수 있다.
  • Producer : 하나 이상의 카프카 토픽에 데이터를 쓰는 클라이언트
  • Consumer : 하나 이상의 카프카 토픽에 데이터를 읽는 클라이언트
  • Replica : 파티션의 복사본. 파티션은 일반적으로 데이터 손실을 방지하기 위해 하나 이상의 브로커에 복사된다.
  • Leader : 파티션이 하나 이상의 브로커에 복제될 수 있지만 단일 브로커가 해당 파티션의 리더로 선출되며 해당 파티션에 쓰거나 읽을 수 있는 유일한 역할을 한다.
  • Consumer group : groupId로 식별되는 컨슈머 인스턴스 그룹. 수평적으로 확장된 애플리케이션에서 각 인스턴스는 컨슈머가 되며 함께 컨슈머 그룹 역할을 한다.
  • Group Coordinator : 소비할 파티션을 그룹의 컨슈머에게 할당하는 책임이 있는 컨슈머 그룹의 인스턴스
  • Offset : 파티션 로그의 특정 지점으로, 컨슈머가 메시지를 소비하면 해당 오프셋을 “커밋”한다. 즉, 컨슈머 그룹이 해당 메시지를 소비했음을 브로커에게 알린다. 컨슈머 그룹이 다시 시작되면 커밋된 가장 높은 오프셋에서 다시 시작된다.
  • Rebalance : 컨슈머가 컨슈머 그룹에 가입 혹은 탈퇴할 경우(ex: 부팅 혹은 종료 등) 그룹은 리밸런스를 수행해야 한다. 즉, 그룹 코디네이터를 선택하고 컨슈머 그룹의 구성원에게 파티션을 할당해야 한다.
  • Heartbeat : 클러스터가 어떤 소비자가 활성 상태인지 확인하는 매커니즘. 때때로 각 컨슈머는 클러스터 리더에게 하트비트 요청을 보내야 한다. 일정 기간 동안 요청을 보내지 않을 경우 죽은 것으로 간주하고 컨슈머 그룹에서 제거되며 리밸런스된다.

동작 방식

  1. 프로듀서가 새로운 메시지를 카프카로 보냄
  2. 프로듀서가 보낸 메시지는 카프카에 컨슈머 큐(토픽을 기준으로 분류)에 도착해 저장됨
  3. 컨슈머는 카프카 서버에 접속해 새로운 메시지를 가져감

NestJS - microservices

microservices란

  • NestJS 프레임워크에서 소규모의 독립적인 서비스(microservices)의 기능 구현을 위해 제공하는 모듈
  • mqtt, redis, kafka, NATS 같은 서비스 연동이 가능함

메시지 형식

토픽의 데이터를 “메시지”라고 부르지만 메시지가 취하는 일정한 형태는 없다. 카프카의 관점에서 메시지는 단지 key-value 형태일 뿐이며 key와 value 모두 바이트 시퀀스일 뿐이다. 메시지의 형식은 프로듀서와 컨슈머에게 달려 있다. 일반적으로 JSON과 같은 강제 스키마가 있는 이진 형식의 일반 텍스트 스키마 없는 메시지를 찾을 수 있다.

JSON

메시지 Buffer를 문자열로 변환하고 구문 분석을 하는 것 외에는 해야할 일이 없다.

await producer.send({
  topic,
  messages: [
    {
      key: "my-key",
      value: JSON.stringify({ some: "data" }),
    },
  ],
});

const eachMessage = async ({ /*topic, partition,*/ message }) => {
  // From Kafka's perspective, both key and value are just bytes
  // so we need to parse them.
  console.log({
    key: message.key.toString(),
    value: JSON.parse(message.value.toString()),
  });

  /**
   * { key: 'my-key', value: { some: 'data' } }
   */
};

JSON의 단점은 어떤 종류의 스키마도 적용하지 않는다는 것이다. 따라서 메시지를 구문 분석한 후에는 어떤 필드가 사용 가능한 지, 어떤 유형이 있는지 알 수 없다. 데이터 프로듀서는 필드가 있거나 해당 유형이 변경되지 않는다는 보장을 하지 않으므로 작업하기 어렵고 오류가 발생하기 쉽다.

AVRO

정의된 스키마에 따라 메시지를 압축 이진 형식으로 변환하는 데이터 직렬화 시스템이다. 이를 통해 컨슈머는 각 메시지에 포함된 내용을 정확히 알 수 있고, 프로듀서는 스키마에 대한 잠재적인 손상 변경을 수행할 때 이를 알 수 있다.

@namespace("com.kafkajs.fixtures")
protocol SimpleProto {
  record Simple {
    string foo;
  }
}

메시지를 인코딩 혹은 디코딩하기 위해 프로듀서 혹은 컨슈머가 올바른 스키마에 액세스 할 수 있어야 한다. 파일에서 직접 읽거나 중앙 스키마 레지스트리에서 가져올 수 있다. 그렇다면 메시지에는 해당 스키마를 차즌 데 사용할 수 있는 인코딩에 사용된 스키마 ID가 포함되어야 한다.

NodeJS의 경우 일반적으로 confluent-schema-registry를 사용하여 수행한다.

Reference