我有一个如下所示的拓扑:
KTable<ByteString, User> users = topology.table(USERS);
KStream<ByteString, JoinRequest> joinRequests = topology.stream(JOIN_REQUESTS)
.mapValues(entityTopologyProcessor::userNew)
.to(USERS);
topology.stream(SETTINGS_CONFIRM_REQUESTS)
.join(users, entityTopologyProcessor::userSettingsConfirm)
.to(USERS);
topology.stream(SETTINGS_UPDATE_REQUESTS)
.join(users, entityTopologyProcessor::userSettingsUpdate)
.to(USERS);
在运行时,此拓扑可正常工作。用连接请求创建用户。他们通过设置确认请求确认其设置。他们使用设置更新请求更新其设置。
但是,重新处理此拓扑不会产生原始结果。具体来说,设置更新joiner没有看到设置确认joiner导致的用户,即使在时间戳方面,从创建用户开始经过很多秒,到用户确认到用户更新时间的时间他们的设置。
我很茫然。我已经尝试关闭用户表上的缓存/日志记录。不知道如何正确地进行这种重新处理。
答案 0 :(得分:2)
KStream-KTable连接不是100%确定性的(并且可能永远不会变为100%确定性)。我们意识到了这个问题并讨论了解决方案,至少可以缓解这个问题。
一个问题是,如果消费者从代理获取,我们无法轻易控制代理返回数据的主题和/或分区。根据我们从代理接收数据的顺序,结果可能略有不同。
一个相关问题:https://issues.apache.org/jira/browse/KAFKA-3514
此博客文章也可能有所帮助:https://www.confluent.io/blog/crossing-streams-joins-apache-kafka/
答案 1 :(得分:0)
我能够通过用以下代码替换代码中的代码来部分解决我的问题:
KTable<ByteString, User> users = topology.table(JOIN_REQUESTS)
.mapValue(entityTopologyProcessor::user)
.leftJoin(topology
.stream(CONFIRM_SETTINGS_REQUESTS)
.groupByKey()
.reduce((a, b) -> b),
entityTopologyProcessor::confirmSettings)
.leftJoin(topology
.stream(SETTINGS_UPDATE_REQUESTS)
.groupByKey()
.reduce(entityTopologyProcessor::settingsUpdateReduce),
entityTopologyProcessor::settingsUpdate);
此解决方案利用了所有表 - 表连接都是确定性的事实。在重新处理期间,结果状态可能暂时不正确,但是一旦拓扑被捕获,最终值就是正确的(给定结果的最终时间戳仍然不是确定性的)。一般来说,这种方法将给定实体(在此示例中为用户)的所有事件(在此示例中:加入请求,确认设置请求,设置更新请求)分组到单个任务中,并将其累积连接到单个产品中。此示例可以通过在结尾处连接另一个流来删除事件来扩展删除事件。
除了这种方法,通常,编写可重新处理的拓扑需要在两个维度上考虑拓扑:实时和重新处理时间。从Kafka Streams 1.0.0开始,这对于开发人员来说是一门艺术。