[apache-kafka] Kafka에 메시지를 보내는 데 키가 필요합니까?

KeyedMessage<String, byte[]> keyedMessage = new KeyedMessage<String, byte[]>(request.getRequestTopicName(), SerializationUtils.serialize(message));
producer.send(keyedMessage);

현재 키가있는 메시지의 일부로 키없이 메시지를 보내고 있는데, 여전히 작동 delete.retention.ms합니까? 메시지의 일부로 키를 보내야합니까? 메시지의 일부로 키를 만드는 것이 좋은가요?



답변

키에 대한 강력한 순서가 필요하고 상태 머신과 같은 것을 개발하는 경우 키는 대부분 유용 / 필요합니다. 동일한 키 (예 : 고유 ID)를 가진 메시지가 항상 올바른 순서로 표시되어야하는 경우 메시지에 키를 첨부하면 동일한 키를 가진 메시지가 항상 주제의 동일한 파티션으로 이동합니다. Kafka는 파티션 내에서 순서를 보장하지만 토픽의 파티션 간에는 순서를 보장하지 않으므로 키를 제공하지 않으면 파티션간에 라운드 로빈 배포가 이루어 지므로 이러한 순서가 유지되지 않습니다.

상태 시스템의 경우 log.cleaner.enable 과 함께 키를 사용 하여 동일한 키를 가진 항목을 중복 제거 할 수 있습니다 . 이 경우 Kafka는 애플리케이션이 지정된 키의 가장 최근 인스턴스에만 관심이 있다고 가정하고 로그 클리너는 키가 null이 아닌 경우에만 지정된 키의 이전 복제본을 삭제합니다. 이 형식의 로그 압축은 log.cleaner.delete.retention 속성에 의해 제어되며 키가 필요합니다.

또는 기본적으로 사용되는 보다 일반적인 특성 log.retention.hours 는 오래된 로그의 전체 세그먼트를 삭제하여 작동합니다. 이 경우 키를 제공 할 필요가 없습니다. Kafka는 지정된 보존 기간보다 오래된 로그 청크 만 삭제합니다.

즉, 로그 압축을 활성화 했거나 동일한 키를 사용하는 메시지에 대해 엄격한 순서가 필요한 경우 반드시 키를 사용해야합니다. 그렇지 않으면 null 키가 더 나은 배포를 제공하고 일부 키가 다른 키보다 더 많이 나타날 수있는 경우 잠재적 인 핫스팟 문제를 방지 할 수 있습니다.


답변

매우 유용한 답변 외에도 몇 가지 세부 정보를 추가하고 싶습니다.

파티셔닝

기본적으로 Kafka는 메시지의 키를 사용하여 기록하는 주제의 파티션을 선택합니다. 이것은에 DefaultPartitioner의해 수행 됩니다

kafka.common.utils.Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;

제공된 키가 없으면 Kafka는 데이터를 라운드 로빈 방식으로 무작위로 분할합니다.

Kafka에서는 Partitioner클래스 를 확장하여 자신 만의 Partitioner를 만들 수 있습니다 . 이를 위해 partition서명이있는 메서드 를 재정의해야합니다 .

int partition(String topic,
              Object key,
              byte[] keyBytes,
              Object value,
              byte[] valueBytes,
              Cluster cluster)

일반적으로 Kafka 메시지 의 는 파티션을 선택하는 데 사용됩니다. 키가 없으면 처리하기 훨씬 더 복잡한 값에 의존해야합니다.

주문

주어진 답변에서 언급했듯이 Kafka는 파티션 수준에서만 메시지 순서를 보장합니다.

두 개의 파티션이있는 Kafka 주제에 고객의 금융 거래를 저장한다고 가정 해 보겠습니다. 메시지는 다음과 같을 수 있습니다 (key : value).

null:{"customerId": 1, "changeInBankAccount": +200}
null:{"customerId": 2, "changeInBankAccount": +100}
null:{"customerId": 1, "changeInBankAccount": +200}
null:{"customerId": 1, "changeInBankAccount": -1337}
null:{"customerId": 1, "changeInBankAccount": +200}

키를 정의하지 않았으므로 두 파티션은 아마도 다음과 같이 보일 것입니다.

// partition 0
null:{"customerId": 1, "changeInBankAccount": +200}
null:{"customerId": 1, "changeInBankAccount": +200}
null:{"customerId": 1, "changeInBankAccount": +200}

// partition 1
null:{"customerId": 2, "changeInBankAccount": +100}
null:{"customerId": 1, "changeInBankAccount": -1337}

해당 주제를 읽는 소비자가 특정 시간에 계정 잔액이 600이라고 말할 수 있습니다. 파티션 1의 메시지 이전에 파티션 0의 모든 메시지를 읽었 기 때문입니다.

의미있는 키 (예 : customerId)를 사용하면 분할이 다음과 같으므로 피할 수 있습니다.

// partition 0
1:{"customerId": 1, "changeInBankAccount": +200}
1:{"customerId": 1, "changeInBankAccount": +200}
1:{"customerId": 1, "changeInBankAccount": -1337}
1:{"customerId": 1, "changeInBankAccount": +200}

// partition 1
2:{"customerId": 2, "changeInBankAccount": +100}

로그 압축

메시지의 일부로 키가 없으면 주제 구성 cleanup.policy을 로 설정할 수 없습니다 compacted. 문서 에 따르면 “로그 압축은 Kafka가 항상 단일 토픽 파티션에 대한 데이터 로그 내의 각 메시지 키에 대해 마지막으로 알려진 값을 유지하도록합니다.”

이 멋지고 유용한 설정은 키 없이는 사용할 수 없습니다.

키 사용

실제 사용 사례에서 Kafka 메시지의 키는 비즈니스 로직의 성능과 명확성에 큰 영향을 미칠 수 있습니다.

예를 들어 키는 데이터 분할에 자연스럽게 사용될 수 있습니다. 소비자가 특정 파티션에서 읽도록 제어 할 수 있으므로 이는 효율적인 필터 역할을 할 수 있습니다. 또한 키에는 후속 처리를 제어하는 ​​데 도움이되는 메시지의 실제 값에 대한 일부 메타 데이터가 포함될 수 있습니다. 키는 일반적으로 값보다 작으므로 전체 값 대신 키를 구문 분석하는 것이 더 편리합니다. 동시에 모든 직렬화 및 스키마 등록을 키를 사용하여 값에 적용 할 수 있습니다.

참고로 정보를 저장하는 데 사용할 수있는 헤더 개념도 있습니다 . 문서를 참조하십시오 .


답변

메시지가있는 키는 기본적으로 특정 필드에 대한 메시지 순서를 가져 오기 위해 전송됩니다.

  • key = null이면 데이터가 라운드 로빈으로 전송됩니다 (다른 파티션과 분산 환경의 다른 브로커 및 물론 동일한 주제로).
  • 키가 전송되면 해당 키에 대한 모든 메시지는 항상 동일한 파티션으로 이동합니다.

설명 및 예

  • key는 임의의 문자열 또는 정수 등이 될 수 있습니다. 정수 employee_id의 예를 키로 사용합니다.
  • 따라서 emplyee_id 123은 항상 파티션 0으로 이동하고 employee_id 345는 항상 파티션 1로 이동합니다. 이는 파티션 수에 따라 달라지는 키 해싱 알고리즘에 의해 결정됩니다.
  • 키를 보내지 않으면 메시지는 라운드 로빈 기술을 사용하여 모든 파티션으로 이동할 수 있습니다.


답변