簡體   English   中英

Kafka Stream 和 KTable 一對多關系連接

[英]Kafka Stream and KTable One-to-Many Relationship Join

我有一個 kafka 流——比如博客和一個 kafka 表——比如與這些博客相關的評論。 來自 kafka 流的鍵可以映射到 Kafka 表中的多個值,即一個博客可以有多個評論。 我想將這兩個連接起來並創建一個帶有注釋 ID 數組的新對象。 但是當我加入時,流只包含最后一個評論 ID。 是否有任何文檔或示例代碼可以為我指明如何實現這一目標的正確方向? 基本上,是否有任何文檔詳細說明如何使用 Kafka 流和 Kafka 表進行一對多關系連接?

KStream<Integer, EnrichedBlog> joinedBlogComments = blogsStream.join(commentsTbl,
              (blogId, blog) -> blog.getBlogId(),
              (blog, comment) -> new EnrichedBlog(blog, comment));

所以,而不是評論 - 我需要有一組評論 ID。

我在您的代碼示例中找不到簽名匹配的 join 方法,但我認為這是問題所在:

KTables 被解釋為一個 changlog,也就是說,每一個具有相同 key 的下一條消息都被解釋為對記錄的更新,而不是新的記錄。 這就是為什么您只能看到給定鍵(博客 ID)的最后一條“評論”消息,之前的值被覆蓋。 為了克服這個問題,您首先需要改變填充 KTable 的方式。 您可以做的是將您的評論主題作為 KStream 添加到您的拓撲中,然后執行聚合,只需構建一個數組或共享相同博客 ID 的評論列表。 該聚合返回一個 KTable,您可以使用它加入您的博客 KStream。

這是一個草圖,您可以如何構建列表值 KTable:

builder.stream("yourCommentTopic") // where key is blog id
.groupByKey()
.aggregate(() -> new ArrayList(), 
    (key, value, agg) -> new KeyValue<>(key, agg.add(value)),
    yourListSerde);

列表在聚合中比數組更容易使用,因此我建議您在需要時將其轉換為下游數組。 您還需要為您的列表提供一個 serde 實現,在上面的示例中為“yourListSerde”。

如果您將 avro 與架構注冊表一起使用,您應該編寫自己的聚合器,因為 kafka 流無法序列化 ArrayList。

    val kTable = aStream
        .groupByKey()
        .aggregate(
                {
                    YourAggregator() // initialize aggregator
                },
                { _, value, agg ->
                    agg.add(value) // add value to a list in YourAggregator
                    agg
                }
        )

然后將kTable與您的其他流 ( bStream ) 一起加入。

    bStream
        .join(
                kTable,
                { b, a ->
                    // do your value join from a to b
                    b
                }
        )

抱歉,我的代碼片段是用 Kotlin 編寫的。

正如上面 Michal 的正確答案所指出的,在這種情況下,不能使用由KTable鍵控的blogId來跟蹤博客,因為只有最新的博客值保留在此類表中。

作為對他的回答中提到的解決方案的建議優化,請注意,如果每個博客有很多評論,在.aggregate()中保持不斷增長的 List 可能會在數據大小和時間上變得昂貴。 這是因為在幕后,該聚合的每次迭代都會導致List的不斷增長的實例,由於數據重用,這在 java 或 scala 中是可以的,但是每個實例都單獨序列化到底層狀態存儲。 示意性地,假設某個鍵已經說了 10 條評論,那么這個表達式被調用了 10 次:

(key, value, agg) -> new KeyValue<>(key, agg.add(value))

每次生成大小為 1,然后是 2,然后... ...然后是 10 的列表,每個列表都獨立地序列化到引擎蓋下的狀態存儲,這意味着1+2+3+...+10=55值將全部序列化(好吧,也許有一些優化,其中一些序列化被跳過,我不知道,盡管我認為空間和時間復雜度是一樣的)。

另一種更復雜的方法是在狀態存儲中使用range scans ,這使得數據結構看起來有點像(partition_key, sort_key)等鍵值存儲中的(partition_key, sort_key) ,其中我們用一個鍵存儲每個評論,例如(blogId, commentId) 在這種情況下,您仍然會通過blogId keyBy()評論流,然后.transform(...)將其傳遞給處理器 API,在那里您可以應用范圍掃描的想法,每次添加(即序列化)一個對狀態存儲的補充注釋,而不是整個列表的新實例。

當我們描繪許多(blogId, commentId)鍵的實例時,一對多關系變得非常明顯,所有實例都具有相同的blogId和不同的commentId ,都存儲在同一個物理節點的同一個狀態存儲實例中,並且這個對於很多節點中的很多blogId ,整個事情都是並行發生的。

我在我的博客上提供了有關該模式的更多詳細信息: 一對多 Kafka Streams Ktable join ,並且我在 github 中放置了一個完整的工作示例

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM