Kafka KStream-KTable加入竞争条件

时间:2019-01-29 00:00:45

标签: apache-kafka apache-kafka-streams

我有以下内容:

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的集合”并使用

阅读主题B
B.groupByKey()
 .aggregate(() -> new CollectionOfB(), (id, b, agg) -> agg.add(b));
 .join(tableA, ...);

现在,我们有了一个KTable-KTable联接,它不受此竞争条件的影响。我认为这是“丑陋”的原因是因为每次加入后,我都必须向主题B发送一条特殊消息,该消息本质上说“删除刚刚从集合中处理的事件”。如果未将此特殊消息发送给主题B,则该集合将继续增长,并且每次加入都会报告该集合中的每个事件。

目前,我正在调查窗口联接是否有效(将A和B都读入KStreams并使用窗口联接)。我不确定这是否会起作用,因为窗口的大小没有上限。我想说的是,“窗口在“之前”开始1秒钟,在“之后”结束无限秒”。即使我可以以某种方式使它工作,但我还是有点担心拥有无边界窗口的空间要求。

任何建议将不胜感激。

1 个答案:

答案 0 :(得分:0)

不确定使用的是哪个版本,但最新的Kafka 2.1改进了stream-table-join。甚至2.1之前,下式成立:

  • 流表联接基于事件时间
  • Kafka Streams按事件时间顺序处理消息
  • 如果要确保首先更新表,则表更新记录的时间戳应比流记录的时间戳小

从2.1开始:

  • 为了允许一些延迟,您可以配置max.task.idle.ms配置以在只有一个输入主题具有输入数据的情况下延迟处理。

在2.0及更早版本中,事件时间处理顺序是尽力而为的,这可能导致您描述的竞争状况。在2.1中,处理顺序是有保证的,只有在max.task.idle.ms命中的情况下才会被违反。

有关详细信息,请参见https://cwiki.apache.org/confluence/display/KAFKA/KIP-353%3A+Improve+Kafka+Streams+Timestamp+Synchronization