向Kafka发送消息时是否需要密钥?


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

目前,我正在发送不带任何键的消息作为键控消息的一部分,它仍然可以使用delete.retention.ms吗?我是否需要发送密钥作为消息的一部分?将密钥作为消息的一部分,这很好吗?

Answers:


172

如果您需要一个强大的按键顺序并且正在开发诸如状态机之类的东西,那么按键通常是有用/必需的。如果您要求始终以正确的顺序查看具有相同密钥(例如,唯一ID)的消息,则将密钥附加到消息将确保具有相同密钥的消息始终进入主题的同一分区。Kafka保证分区中的顺序,但不能保证主题中跨分区的顺序,因此,不提供键(将导致跨分区的循环分布)将不会保持这种顺序。

对于状态机,可以将键与log.cleaner.enable一起使用,以对具有相同键的条目进行重复数据删除。在这种情况下,Kafka假定您的应用程序仅关心给定键的最新实例,并且日志清理器仅在键不为null时才删除给定键的较旧副本。这种日志压缩形式由log.cleaner.delete.retention属性控制,并且需要密钥。

另外,默认情况下启用的更常见的属性log.retention.hours通过删除日志的完整段来工作。在这种情况下,不必提供密钥。Kafka将仅删除早于给定保留期限的日志块。

就是说,如果您启用了日志压缩或对具有相同密钥的消息要求严格的顺序,那么您肯定应该使用密钥。否则,在某些键可能比其他键出现更多的情况下,空键可能会提供更好的分配并防止潜在的热点问题。


我是Kafka的新手,这是为什么要问很多问题的原因:有两个问题:第一个问题,我们能否在关键的基础上使用消息,目前我正在使用MessagAndMetadata mm的消息。还是在使用消息时忽略密钥是可以的。我正在使用hig Level Consumer Api。
gaurav 2015年

1
@kuujo我假设此重复数据删除仅用于日志条目,它是否不一定要对主题队列中的消息重复数据删除?
user1658296 '16

2
messages使消息依次进入同一分区对于处理非符号更新很重要,例如,客户选择交货日期(一条消息),但稍后改变主意(第二条消息)。如果消息要去到不同的分区,则可以首先/最后处理任一消息,例如,每个分区消耗2个使用者。如果与同一传递有关的两条消息都进入同一分区,则将按照先进先出的顺序处理它们,并给出正确的最终传递日期。
库纳尔

3
顺序保证不是来自密钥,而是来自同一分区中的消息。消息到分区的路由不必基于密钥。您可以在创建ProducerRecord
Malt

2
我的理解是,生产者客户端负责选择分区(kafka.apache.org/documentation.html#design_loadbalancing),该分区可以或可以不基于密钥。那么,为什么说订购钥匙是必需的呢?
lfk

5

除了非常有用的已接受答案外,我还要添加一些其他详细信息

分区

默认情况下,Kafka使用消息的键来选择要写入的主题的分区。这是通过类似的方式完成的

hash(key) % number_of_partitions

如果没有提供密钥,则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,尽管从来没有这样!只是因为它正在读取分区0中的所有消息,然后才读取分区1中的消息。

使用有意义的键(例如customerId),可以避免这种情况,因为partitoning如下所示:

// 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.policycompacted。根据文档, “日志压缩可确保Kafka将始终为单个主题分区的数据日志保留至少每个消息密钥的最后一个已知值”。

没有任何按键,将无法使用此好用且有用的设置。

按键用法

在实际的用例中,Kafka消息的密钥可能会对您的性能和业务逻辑的清晰度产生巨大影响。

例如,可以自然地使用密钥对数据进行分区。由于可以控制使用者从特定分区读取数据,因此可以用作有效的筛选器。此外,键可以在消息的实际值上包含一些元数据,以帮助您控制后续处理。键通常比值小,因此解析键而不是整个值更方便。同时,您可以将所有序列化和模式注册应用到您的值中,也可以通过密钥进行。

需要注意的是,还有Header概念可用于存储信息,请参阅文档

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.