我有以下内容:
KTable<Integer, A> tableA = builder.table("A");
KStream<Integer, B> streamB = builder.stream("B");
streamB中的消息需要使用来自tableA的数据来充实。
示例数据:
Topic A: (1, {name=john})
Topic B: (1, {type=create,...}), (1, {type=update,...}), (1, {type=update...})
在一个完美的世界里,我想做
streamB.join(tableA, (b, a) -> { b.name = a.name; return b; })
.selectKey((k,b) -> b.name)
.to("C");
不幸的是,这对我来说不起作用,因为我的数据是,每当一条消息写入主题A时,相应的消息也会写入主题B(源是单个DB事务)。现在,在此初始“创建”交易之后,主题B将继续接收更多消息。有时,主题B上每秒会出现几个事件,但是对于给定的键,也可能有连续几个小时的连续事件。
简单解决方案不起作用的原因是,原始的“创建”事务导致竞争状态:主题A和B几乎同时获得其消息,并且如果B消息首先到达拓扑的“加入”部分(例如在A消息到达之前几毫秒),tableA尚不包含相应的条目。此时事件已丢失。我可以在主题C上看到这种情况:有些事件显示,有些则没有(如果我使用leftJoin,则显示所有事件,但有些事件具有null键,这等同于丢失)。这仅是初始“创建”交易的问题。此后,每当事件到达主题B时,表A中就会存在相应的条目。
所以我的问题是:如何解决这个问题?
我当前的解决方案很难看。我要做的是创建了一个“ B的集合”并使用
阅读主题BB.groupByKey()
.aggregate(() -> new CollectionOfB(), (id, b, agg) -> agg.add(b));
.join(tableA, ...);
现在,我们有了一个KTable-KTable联接,它不受此竞争条件的影响。我认为这是“丑陋”的原因是因为每次加入后,我都必须向主题B发送一条特殊消息,该消息本质上说“删除刚刚从集合中处理的事件”。如果未将此特殊消息发送给主题B,则该集合将继续增长,并且每次加入都会报告该集合中的每个事件。
目前,我正在调查窗口联接是否有效(将A和B都读入KStreams并使用窗口联接)。我不确定这是否会起作用,因为窗口的大小没有上限。我想说的是,“窗口在“之前”开始1秒钟,在“之后”结束无限秒”。即使我可以以某种方式使它工作,但我还是有点担心拥有无边界窗口的空间要求。
任何建议将不胜感激。
答案 0 :(得分:0)
不确定使用的是哪个版本,但最新的Kafka 2.1改进了stream-table-join。甚至2.1之前,下式成立:
从2.1开始:
max.task.idle.ms
配置以在只有一个输入主题具有输入数据的情况下延迟处理。在2.0及更早版本中,事件时间处理顺序是尽力而为的,这可能导致您描述的竞争状况。在2.1中,处理顺序是有保证的,只有在max.task.idle.ms
命中的情况下才会被违反。