tl; dr
不,向 Kafka 发送消息时不需要密钥。但...
除了非常有用的公认答案之外,我还想添加更多细节
分区
默认情况下,Kafka 使用消息的键来选择它写入的主题的分区。这是在DefaultPartitioner
by
kafka.common.utils.Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
如果没有提供密钥,则 Kafka 将以循环方式对数据进行分区。
Partitioner
在 Kafka 中,可以通过扩展类来创建自己的 Partitioner 。为此,您需要覆盖partition
具有签名的方法:
int partition(String topic,
Object key,
byte[] keyBytes,
Object value,
byte[] valueBytes,
Cluster cluster)
通常,Kafka 消息的key用于选择分区,返回值(类型为int
)是分区号。如果没有密钥,您需要依赖处理起来可能要复杂得多的值。
订购
如给定答案中所述,Kafka 仅在分区级别保证消息的排序。
假设您想将客户的金融交易存储在具有两个分区的 Kafka 主题中。消息可能看起来像(键:值)
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}
请记住,分区内的排序只有在生产者配置max.in.flight.requests.per.connection
设置为 时才能保证1
。但是,该配置的默认值是,5
它被描述为:
"客户端在阻塞前将在单个连接上发送的未确认请求的最大数量。请注意,如果此设置设置为大于 1 并且发送失败,则存在由于重试而导致消息重新排序的风险(即,如果启用重试)。”
您可以在另一个 Stackoverflow 文章中找到更多详细信息,关于Kafka - Message Ordering Guarantees。
日志压缩
如果没有密钥作为消息的一部分,您将无法将主题配置设置cleanup.policy
为compacted
. 根据文档“日志压缩确保 Kafka 将始终为单个主题分区的数据日志中的每个消息键至少保留最后一个已知值。”。
如果没有任何密钥,这个不错且有用的设置将不可用。
密钥的使用
在实际用例中,Kafka 消息的密钥会对您的性能和业务逻辑的清晰度产生巨大影响。
例如,密钥可以自然地用于对数据进行分区。由于您可以控制您的消费者从特定分区中读取,这可以作为一个有效的过滤器。此外,密钥可以包含一些关于消息实际值的元数据,以帮助您控制后续处理。键通常比值小,因此解析键而不是整个值更方便。同时,您可以应用所有序列化和模式注册,就像使用您的值一样,也可以使用密钥。
作为说明,还有Header的概念可用于存储信息,请参阅文档。