tl;dr 发送消息到Kafka不需要键,但默认情况下会使用消息的键来选择主题的分区。
除了接受的答案外,我想添加一些细节。
分区
默认情况下,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 消息的键用于选择分区,返回值(类型为 int
)是分区号。如果没有键,则需要依赖值进行处理,这可能更加复杂。
排序
正如给出的答案所述,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中的所有消息。
如果使用有意义的键(例如客户ID),则可以避免这种情况,分区将如下所示:
// 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且存在失败的发送,则由于重试可能会导致消息重新排序(即,如果启用了重试)。
您可以在另一个关于Kafka-Message Ordering Guarantees的Stackoverflow帖子中找到更多详细信息。
日志压实
如果您的消息中没有键,则无法将主题配置cleanup.policy
设置为compacted
。根据文档,“日志压实确保Kafka始终保留每个消息键在单个主题分区数据日志中的最后已知值。”。
没有任何键,这个好又有用的设置将不可用。
键的用法
在实际使用情况中,Kafka消息的键可以对业务逻辑的性能和清晰度产生巨大影响。
例如,可以自然地使用键来分区数据。由于可以控制消费者从特定分区读取,因此这可能作为有效的过滤器。此外,键可以包含有关消息实际值的某些元数据,以帮助您控制后续处理。键通常比值小,因此解析键而不是整个值更方便。同时,您可以像处理值一样处理键,进行所有序列化和模式注册。
需要注意的是,还存在头部的概念可用于存储信息,请参阅文档。
ProducerRecord
时明确指定一个分区。 - Malt