我是Kafka Streams的新手,我使用的是1.0.0版本。我想从其中一个值为KTable设置一个新密钥。
使用KStream时,可以通过使用方法selectKey()来完成。
kstream.selectKey ((k,v) -> v.newKey)
但是KTable中缺少这种方法。唯一的方法是将给定的KTable转换为KStream。有关这个问题的任何想法?它改变了KTable设计的关键?
答案 0 :(得分:6)
如果要设置新密钥,则需要重新分组KTable:
KTable newTable = table.groupBy(/*put select key function here*/)
.aggregate(...);
因为KTable的密钥必须是唯一的(与KStream不同),所以需要指定一个聚合函数,该函数将具有相同(新)密钥的所有记录聚合为单个值。
答案 1 :(得分:1)
对于使用融合5.5。+的用户,有一种方法可以从流中提取密钥并直接转换为KTable:
KTable<String, User> userTable = builder
.stream("topic_name", Consumed.with(userIdSerde, userSerde))
.selectKey((key, value) -> key.getUserId())
.toTable( Materialized.with(stringIdSerde, userSerde));
可以找到详细信息here
答案 2 :(得分:1)
我认为@Matthias 描述的方式不够准确/详细。这是正确的,但这种限制的根本原因(也存在于 ksqlDB
CREATE TABLE
语法中)不仅仅是因为 KTable 的键必须是唯一的。
唯一性本身并不限制KTables
。毕竟,任何底层主题都可以并且经常包含具有相同键的消息。
KTable
对此没有任何问题。它只会为每个密钥强制执行最新状态。这样做有多种后果,包括从聚合函数构建的 KTable
可以基于单个输入消息在其输出主题中生成多条消息...但让我们回到您的问题。
因此,KTable 需要知道特定密钥的哪条消息是最后一条消息,这意味着它是密钥的最新状态。
Kafka 有哪些排序保证?正确,基于每个分区。
重新键入消息时会发生什么?正确,它们将分布在与输入消息非常不同的分区中。
因此,具有相同密钥的初始消息由代理本身正确存储到同一分区中(如果您没有对自定义 Partitioner
做任何花哨/愚蠢的事情)
这样 KTable
始终可以推断出最新状态。
但是如果消息在运行中的 Kafka Streams 应用程序中重新键入会发生什么?
它们将再次跨分区传播,但现在使用不同的键,如果您的应用程序横向扩展并且您有多个并行工作您根本无法保证新键的最后一条消息是实际上是存储在原始主题中的最后一条消息。单独的任务没有这样的协调。他们不能。否则效率会很低。
因此,如果允许此类重新键入,KTable
将失去其主要语义。
答案 3 :(得分:0)
@Matthias的回答使我走了正确的道路,但我认为在这里提供示例代码可能会有所帮助
final KTable<String, User> usersKeyedByApplicationIDKTable = usersKTable.groupBy(
// First, going to set the new key to the user's application id
(userId, user) -> KeyValue.pair(user.getApplicationID().toString(), user)
).aggregate(
// Initiate the aggregate value
() -> null,
// adder (doing nothing, just passing the user through as the value)
(applicationId, user, aggValue) -> user,
// subtractor (doing nothing, just passing the user through as the value)
(applicationId, user, aggValue) -> user
);
答案 4 :(得分:0)
@Allen Underwood代码帮助了我,如果密钥是自定义Pojo,则必须进行一些更改。当我上课时,出现了异常。下面的代码有效
usersKTable.groupBy((k, v) -> KeyValue.pair(v.getCompositeKey(), v),Grouped.with(compositeKeySerde,valueSerde))
.aggregate(
() -> null,
(applicationId, value, aggValue) -> value,
(applicationId, value, aggValue) -> value,
Materialized.with(compositeKeySerde, valueSerde)
);