【问题标题】:Using different key for each message in kafka对kafka中的每条消息使用不同的密钥
【发布时间】:2020-10-18 18:17:23
【问题描述】:

我们在 Java 中有一个 kafka 生产者-消费者设置(使用 spring-kafka,但这里可能不相关)。使用的键是String,值是自定义 POJO。生产者-消费者通过具有 16 个分区的单个主题 test-topic 进行通信。消费者具有 16 的并发,因此它可以从每个分区并行读取。

从文档和其他参考资料中,我了解到 - 使用null 密钥将以循环方式在分区中分发发布者发送的消息。如果我有兴趣将消息分发到使用键派生的特定分区,建议使用非空键

我在这里有以下疑问-

  1. 目前,生产者为每条消息发送唯一的密钥。由于这是每条消息唯一的String,因此在几乎所有情况下,它也会生成唯一的哈希码。消息将如何在这里分发,是像 null 键这样的循环还是一些重复使用分区或任何其他控制机制的哈希逻辑?
  2. 使用上面提到的策略键而不是null键有什么优点或缺点吗?
  3. 我不需要维护消息的顺序或分组。在这种情况下,使用 null 键是否明智,或者每条消息有一个唯一或非唯一的非空键仍然很好,如果是,为什么?
  4. 消费者正在批量读取消息。有或没有关键影响批量读取与单次读取有什么不同?

【问题讨论】:

    标签: java apache-kafka kafka-producer-api


    【解决方案1】:

    Kafka生产者根据DefaultPartitioner,自定义分区器向特定分区发送消息,或者在发送消息时传递分区信息以写入特定分区。 将键定义为 null 或非 null 取决于您的用例和要求,但关键目的是将您的消息分布在不同的分区上,以供消费者组的多个消费者使用。

    非空键确保相似的键将停放在同一个分区上,这将帮助您将同一桶上的多个相似键分组给进一步的分析人员,同时空键使您可以均匀地分配消息。

    非空键总是有助于传递消息的元细节以供进一步处理。我希望通过自定义分区器传递非空键来控制消息流。但这取决于具体要求,如果你想传递 key null 那绝对没问题。

    注意:在未来的 Apache Kafka (2.5) 版本中,您可以定义 RoundRobin 分区器作为不需要的分区策略(KIP-369) 到键为空。 https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=89070828

    1. 如果您未定义自定义分区器,它将使用默认分区器

    在 Apache Kafka 2.4 之前,它会一个接一个地循环,将记录发送给每个人。 在这种情况下,Apache Kafka 2.4 之前的旧分区策略是循环遍历主题的分区并向每个分区发送一条记录。 但是,正如您所理解的,消息作为一个带有配置参数 linger.ms 的批次发送,它可能会对小批量的性能产生影响,因为每个批次都会进入特定的分区,因此 Apache Kafka 引入了新的 Sticky 分区器以防空键

    Apache Kafka 在 Apache Kafka 2.4 中引入了 Sticky Partitioner(KIP-480),以防默认分区器中的 key 为空,如下所述

    粘性分区策略

    粘性分区器通过选择单个分区来发送所有非键记录,解决了将没有键的记录分散到更小的批次中的问题。一旦该分区的批次被填充或以其他方式完成,粘性分区器会随机选择并“粘贴”到新分区。这样,在更长的时间内,记录大致均匀分布在所有分区中,同时获得更大批量大小的额外好处。

    Please click here for more detail

    1. 如果您传递一个非空键并且未定义自定义分区器,它将使用 DefaultPartitioner 来识别分区以发布消息。 DefaultPartitioner 使用 MurmurHash,这是一种非加密哈希函数,通常用于基于哈希的查找。然后将此哈希用于模运算 (% numPartitions) 以确保返回的分区在 [0, N] 范围内,其中 N 是主题的分区数。

      返回 Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;

    2. 您还可以定义自定义分区并实现逻辑来选择分区 https://kafka.apache.org/10/javadoc/org/apache/kafka/clients/producer/Partitioner.html

    3. 在发布消息时显式传递分区

      /** * 创建一条记录发送到指定主题和分区 */ public ProducerRecord(String topic, Integer partition, K key, V value) { this(主题,分区,空,键,值,空); }

    【讨论】:

      【解决方案2】:

      客户端控制它将消息发布到哪个分区。这个可以 随机完成,实现一种随机负载均衡,或者 可以通过一些语义划分功能来完成。我们揭露 通过允许用户指定一个语义划分接口 分区的键并使用它来散列到分区(有 如果需要,还可以选择覆盖分区函数)。

      密钥用于将消息拆分到不同的分区。根据我的经验,如果使用 null 键,它们将全部在 Partition 0 中结束。

      Null Keys 不是一个好的设计选择,不应在生产中使用。也许只是为了快速制作原型。

      粘性分区策略 粘性分区器通过选择单个分区来发送所有非键记录来解决将没有键的记录分散到更小的批次的问题

      public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster,
                           int numPartitions) {
          if (keyBytes == null) {
              return stickyPartitionCache.partition(topic, cluster);
          }
      

      【讨论】:

      • With a null key they will end all in the Partition 0 from my experience - 不,Kafka生产者将使用循环策略
      • 有很多分区策略,你甚至可以定义自己的分区语义。试一试,你就会看到。
      • 同意我们可以指定自定义分区策略,但我说的是默认生产者实现DefaultPartitioner
      • @VasiliySarzhynskyi 不同版本之间存在不匹配。这是旧的行为,你是对的。似乎当前版本实现了粘性分区以获得更好的性能...
      【解决方案3】:

      如果您指定 key,默认情况下,分区将基于 key modulo number of partitions (hash(key) % partitions_number) 的哈希值进行选择。通过拥有唯一键,您将按分区均匀分布消息。您还可以通过为您的自定义分区程序提供所需的逻辑来覆盖分区行为。

      根据您的描述,您不需要密钥,在这种情况下使用null,它会稍微:1)节省Kafka集群上的资源(不存储不需要的密钥),2)减少网络延迟, 3)应用程序不会生成唯一的密钥,也不会从中计算哈希(如果你有密钥,所有这些点都是次要的并且不重要)。如果您想对具有相同密钥的消息进行排序(但不是您的情况,因为您有唯一的密钥),或者您有基于此密钥的业务逻辑,则应该使用密钥。批处理可以使用 null 键正常工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-09-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-06
        • 1970-01-01
        相关资源
        最近更新 更多