ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kafka 101] 카프카 브로커 (Kafka Broker)
    개발자 라이프/카프카 2020. 3. 29. 23:13
    반응형

    들어가며

     카프카는 메시지를 생산하는 프로듀서와 소비하는 컨슈머, 그리고 그 사이에서 메시지를 저장, 전달하는 브로커(Broker)로 구성됩니다. 이번 글은 카프카의 중추인 브로커에 대해 전반적으로 설명합니다.

     이 글은 카프카의 토픽, 파티션에 관한 지식을 바탕으로 합니다. 따라서 관련된 지식이 부족한 독자분은 이전 글([Kafka 101] 카프카 메시지와 토픽과 파티션)을 먼저 읽어주시길 바랍니다. 

    1. 카프카 브로커

    카프카 브로커는 프로듀서와 컨슈머 사이에서 메시지를 중계합니다. (출처 : https://mapr.com/ebooks/streaming-architecture/chapter-04-apache-kafka-overview.html)

     카프카 브로커는 일반적으로 '카프카'라고 불리는 시스템을 말합니다. 프로듀서와 컨슈머는 별도의 애플리케이션으로 구성되는 반면, 브로커는 카프카 자체이기 때문입니다. 따라서 '카프카를 구성한다' 혹은 '카프카를 통해 메시지를 전달한다'에서 카프카는 브로커를 의미합니다. 

    2. 카프카 (브로커) 클러스터 구성

     브로커는 한 대 이상의 노드로 클러스터를 구성할 수 있습니다. 하지만 현재(2020.03.28 v2.4.1)는 브로커들로만 클러스터를 구성할 수 없습니다. 브로커의 여러 가지 메타 정보를 저장 관리해주는 주키퍼(Zookeeper)가 필요하기 때문입니다. 따라서, 카프카를 구성할 때는 한 대 이상의 주키퍼로 구성된 주키퍼 클러스터와 한 대 이상의 브로커로 구성된 브로커(=카프카) 클러스터로 구성됩니다.

    일반적인 최소 사양으로 주키퍼 3, 카프카 3으로 구성합니다.

    주키퍼

     주키퍼는 현재 카프카에 필수적이기에 간단히 설명하고 넘어가겠습니다. 주키퍼는 아파치 프로젝트 중 하나로 하둡 에코 시스템을 구성합니다. 주 역할은 분산 시스템의 메타 정보를 관리하고, 필요시에는 분산 시스템의 마스터를 선출합니다. 예를 들면, 카프카 클러스터를 구성하면 주키퍼에는 카프카 클러스터의 식별 정보부터 현재 살아있는 브로커 정보, 나아가 권한 정보 등이 저장됩니다. 또한, 카프카 브로커들 중 일종의 지휘자 역할을 하는 컨트롤러(Controller) 브로커를 뽑는 역할을 담당합니다.

    주키퍼 쉘을 통해 카프카 클러스터의 메타 정보를 확인하는 모습.

     주키퍼는 디렉터리 형태로 데이터를 저장, 관리합니다. 그리고 카프카의 메타 정보는 클러스터 구성할 때, 주키퍼 연결 설정(zookeeper.connect)에서 지정된 디렉토리 하위에 저장됩니다. 따라서 동일한 카프카 클러스터는 주키퍼 연결 설정에서 동일한 디렉토리 경로를 가지며, 하나의 주키퍼 클러스터에 여러 카프카 클러스터가 동시에 구성될 수 있습니다. 

    동일한 주키퍼 클러스터에 멀티 카프카 클러스터를 구성하는 모습. 각 클러스터마다 서로 다른 주키퍼 경로를 설정한다.

     현재 카프카 진영에서는 탈 주키퍼를 위한 작업들이 많이 진행되고 있습니다. 개인적인 예상으로는 카프카 3.0.0 버전부터는 주키퍼 없이 카프카 클러스터를 구성할 수 있을 것으로 예상됩니다.

    Peer-to-Peer 구조

     카프카 클러스터는 모든 브로커가 클라이언트의 요청을 처리할 수 있는 Peer-to-Peer(p2p) 구조를 가집니다. 이렇게 p2p 구조가 가능한 것은 클라이언트와 카프카 간 메타 데이터를 전달하는 과정이 있기 때문입니다(참고 : [Kafka 101] bootstrap.servers 설정에 관하여). 다만 염두해야 할 것은 브로커는 p2p 구조인 반면, 실제로 메시지를 전달받고 저장하는 단위인 파티션은 리더와 팔로워로 나뉘어 작업을 처리한다는 것입니다(이전 글 참고). 

    하나의 토픽이라도 리더 파티션의 위치에 따라 여러 브로커가 요청 받을 수 있습니다. (출처 : https://www.confluent.io/blog/hands-free-kafka-replication-a-lesson-in-operational-simplicity/)

    3. 주요 브로커 설정

     브로커는 여러 환경에 따라 다양한 설정을 할 수 있습니다. 이러한 설정의 대부분은 기본값이 제공됩니다. 따라서 별도로 설정해주지 않아도 되지만 다음 3가지 설정은 반드시 설정해줘야 합니다.

    • broker.id
    • log.dirs
    • zookeeper.connect

     broker.id 는 같은 카프카 클러스터에서 현재 브로커를 식별하기 위한 숫자입니다. 따라서 다른 브로커와 다른 숫자를 설정해야 합니다. 일반적으로는 0 혹은 1부터 순차적으로 설정합니다.

     log.dirs 설정은 브로커가 프로듀서로부터 받는 메시지들을 저장할 위치 경로를 지정하는 설정입니다. 기본 값은  /tmp/kafka-logs입니다. 여기서 주의해야 할 것은 기본 값이 /tmp/ 하위에 지정되기 때문에 OS 설정에 따라 임의로 삭제될 수 있습니다. 그러므로 별도의 위치로 꼭 지정해주시길 바랍니다.

     zookeeper.connect 는 카프카 클러스터의 메타 정보를 저장할 주키퍼에 관한 호스트 연결 정보를 가집니다. 호스트 연결 정보는 hostname:port로 구성되며, 동일한 주키퍼 클러스터의 여러 노드를 ', '로 구분한 문자열로 지정할 수 있습니다. 예를 들어 'zookeeper01:2181, zookeeper02:2181'와 같이 지정할 수 있습니다. 또한 앞서 언급한 것처럼 주키퍼 내부의 저장 위치를 함께 설정할 수 있습니다. 이럴 경우에는 'zookeeper01:2181, zookeeper02:2181/my/path'와 같이 설정하면 됩니다.

     이 외에도 여러 설정이 존재하는 데, 개인적으로 중요하다고 생각되는 설정을 몇 가지 더 소개하겠습니다. 

    • advertised.listeners : 클라이언트가 브로커를 바라볼 때의 브로커 호스트 정보.
    • auto.create.topics.enable : 클라이언트가 특정 토픽으로 요청했을 경우 자동 생성 여부. (기본값 true)
    • offsets.topic.replication.factor : 오프셋 토픽의 복제 계수. (기본값 3)

     브로커는 하나 이상의 리스너(listener)를 통해 외부 요청을 받습니다. advertised.listeners 는 클라이언트가 브로커에게 요청할 때, 클라이언트 입장에서 브로커를 찾을 수 있는 호스트 정보를 설정합니다. 이 설정은 초기 메타 데이터 전달 과정에서 클라이언트로 전달되고, 이 정보를 바탕으로 클라이언트가 브로커로 요청합니다. 여기서 중요한 것은 클라이언트와 브로커의 네트워크 환경이 다를 경우, 클라이언트가 브로커를 찾을 수 있도록 설정해줘야 한다는 것입니다. (제가 이 설정 때문에 많이 헤맸습니다.)

    도커 환경에서의 리스너 구성 예시. 도커 네트워크와 로컬 호스트 네트워크 간 별도의 리스너를 구성했을 때, advertised.listeners 설정을 클라이언트가 브로커를 찾을 수 있는 호스트 정보로 설정해야 합니다. (출처 : https://docs.confluent.io/current/kafka/multi-node.html)

    auto.create.topics.enable 는 운영 과정에서 중요한 설정입니다. 실제 상용 환경에서 브로커는 무수히 많은 클라이언트와 맞이하게 됩니다. 그렇게 때문에 적절한 토픽의 관리가 필요한데, 만약 이 설정을 true로 설정하면 클라이언트의 요청에 따라 토픽이 지속적으로 생성됩니다. 이는 운영 상에 큰 골칫거리가 될 수 있겠습니다. 

     offset.topic.replication.factor 는 개발 환경 구성에서 많이 부각되는 설정입니다. 오프셋(offset) 토픽의 복제 계수에 관한 설정인데, 만약 브로커를 1대로 구성하게 되면 기본값이 3과 충돌하여 브로커가 실행되지 않습니다. 복제 파티션들은 같은 브로커에 존재할 수 없기 때문입니다. 그러므로 개발 환경에서 1대로 구성할 경우에는 꼭 해당 설정을 1로 설정해야 합니다.

     이외에도 보안, 로그 관리, 압축 등에 관한 다양하고 중요한 설정들이 있습니다. 자세한 내용은 공식 문서를 참고하시길 바랍니다.

     

    4. 브로커 내부 동작 요소

     카프카 브로커는 프로듀서로부터 메시지를 발행받아 이를 저장하고, 컨슈머로 전달합니다. 이 과정을 위해 브로커 내부에는 다양한 동작 요소들이 존재합니다. 아래는 그 중 3가지를 간단히 정리했습니다.

    컨트롤러

     컨트롤러(Controller)는 하나의 클러스터에서 하나의 브로커에 부여되는 역할로, 마치 지휘자와 같은 역할입니다. 컨트롤러는 브로커들의 생존 여부(liveness)를 체크합니다. 그리고 만약 임의의 브로커가 중단되었을 경우, 해당 브로커에 있었던 리더 파티션을 탈락시키고 다른 팔로워 파티션들 중 하나를 리더로 뽑습니다(leader election). 이 과정은 카프카의 실패 극복(failover) 전략 중 중요한 부분을 담당하기 때문에 컨트롤러의 역할은 중요합니다. 참고로 컨트롤러가 중단되는 경우에는 주키퍼가 이를 감지하여 새로운 컨트롤러를 선출합니다.

    브로커가 종료될 때, 컨트롤러를 통한 복제 파티션의 리더 재선출 과정. 컨트롤러는 변경된 리더 파티션 정보를 주키퍼에 저장하고, 리더 파티션을 조정합니다. (출처 : https://www.slideshare.net/ConfluentInc/a-deep-dive-into-kafka-controller)

    메시지 저장과 메시지 파일 관리

     브로커는 프로듀서로부터 전달되는 메시지를 로그(log) 자료구조 형태로 디스크에 저장합니다. 로그 자료구조는 새로운 쓰기 작업이 중간에 삽입되지 않고 오로지 끝에서만 되는 append-only 특징을 가집니다. 일반적으로 우리가 이야기하는 시스템 로그, 애플리케이션 로그들도 이러한 특징을 가집니다. 로그의 append-only 특징에 관한 자세한 설명은 별도의 글로 정리하도록 하겠습니다. 다만, 가장 중요한 것은 쓰기 작업이 끝에서만 이뤄지므로 브로커에 이미 쓰여진 메시지(로그)는 변경이 불가합니다. 하지만 그렇기 때문에 빠른 쓰기 작업이 가능합니다.

    브로커 파티션에 메시지를 쓰고, 읽는 모습. 각 네모 칸은 메시지를 나타내며, 숫자는 오프셋을 나타냅니다. 쓰기 작업은 오직 끝에서만 진행되며, 오프셋은 메시지 삽입에 따라 순차적으로 증가합니다. (출처 : https://kafka.apache.org/documentation/)

     메시지가 브로커에 저장될 때는 메시지 내용과 함께 오프셋 정보가 저장됩니다. 메시지의 오프셋은 메시지를 구분하는 식별자 역할을 하며, 메시지의 삽입에 따라 0부터 꾸준히 증가합니다. 

     브로커에 저장되는 메시지들은 파티션 별로 세그먼트(segment)라는 파일로 저장됩니다. 예를 들어 토픽 A에 3개의 파티션이 있다면, 각 파티션이 위치한 브로커의 log.dirs 하위에는 해당 파티션의 세그먼트 파일이 존재합니다. 그리고 프로듀서가 파티션으로 발행되는 메시지를 세그먼트 파일에 쓰고, 컨슈머는 이 파일을 읽어감으로써 메시지를 구독합니다.

    프로듀서는 오직 끝에만 쓰며, 컨슈머는 오프셋을 기준으로 차례차례 읽어나갑니다. (출처 : https://kafka.apache.org/documentation/)

    Zero-copy

     카프카는 다른 메시징 큐보다 뛰어난 성능을 제공합니다. 그럴 수 있는 이유 중 하나가 제로 카피(Zero-copy) 기술을 사용하기 때문입니다. 제로 카피 기술은 브로커가 세그먼트 파일로부터 메시지를 읽고, 이를 네트워크로 전달하는 과정에서 문맥 교환(context switch)이 없도록 하는 기술입니다.

    제로 카피가 아닌 경우, 커널 영역과 어플리케이션 영역 간 문맥 교환이 발생합니다.
    제로 카피인 경우 문맥 교환 없이 커널 영역 안에서만 작업이 이뤄집니다. (출처 : https://developer.ibm.com/articles/j-zerocopy/)

     이처럼 카프카는 제로 카피를 통해 더욱 높은 성능을 제공합니다. 다만, 메시지 암호화(SSL)를 설정한 경우에는 메시지 암, 복호화를 위해 어쩔 수 없이 문맥 교환이 발생하므로 제로 카피 기술을 사용할 수 없게 됩니다. 

     

    마무리

     메시지를 저장하고 관리하는 브로커에 대한 구성, 설정, 내부 특징에 대해 정말 간단히 전반적으로 알아봤습니다. 비록 자세한 설명을 담지 못한 부분이 많으나 이는 추후 별도의 글로 정리하도록 하겠습니다. 

    ps 1. 혹시 잘못되거나 부족한 부분은 댓글로 남겨주시길 바랍니다. 
    ps 2. 내용이 마음에 드셨다면 공감 버튼❤️을 눌러주세요!

    반응형

    댓글

Designed by Tistory.