我需要以(userId)为键的缓慢变化的streamA
来充实以(userId,startTripTimestamp)为键的快速变化的streamB
。
我将Flink 1.8与DataStream API一起使用。我考虑两种方法:
广播streamB
,并通过userId和最新时间戳加入流。它等同于TableAPI中的DynamicTable吗?我可以看到此解决方案的一些缺点:streamB
需要适合每个工作程序节点的RAM,因为整个streamB
需要存储在每个工作程序的RAM中,所以它提高了RAM的利用率。
将streamA
的状态概括为仅以(userId)为键的流,我们将其命名为streamC
,以与streamB
具有公用密钥。然后,我可以将streamC
与streamB
结合起来,按处理时间排序,并处理状态下的两种类型的事件。处理一般的流(过程函数中的更多代码)更为复杂,但不会消耗太多的RAM来在所有节点上拥有所有streamB
。他们在该解决方案中还有其他弊端吗?
我还看到了这样的建议https://cwiki.apache.org/confluence/display/FLINK/FLIP-17+Side+Inputs+for+DataStream+API:
通常,大多数此类活动都遵循加入主流的模式 一个或多个缓慢变化的输入或 静态数据:
[...]
加入流与缓慢发展的数据流:这非常类似于 上述情况,但我们用于充实的侧输入是 随着时间的推移而发展。这可以通过等待一些初始数据来完成 在处理主输入和连续输入之前可用 将新数据吸收到内部输入结构中 到达。
不幸的是,要达到此功能https://issues.apache.org/jira/browse/FLINK-6131似乎还需要很长的时间,并且没有其他描述。因此,我想问一下针对所描述用例的当前推荐方法。
我见过Combining low-latency streams with multiple meta-data streams in Flink (enrichment),但是它没有指定该流的键,而且在Flink 1.4时已得到回答,所以我希望推荐的解决方案可能已经改变。
答案 0 :(得分:3)
以Gaurav Kumar已回答的内容为基础。
主要问题是,您是否需要完全匹配streamA
和streamB
中的记录,还是尽力而为?例如,对您来说,这是一个问题吗,由于竞争状况,streamA
中的某些(很多?)记录可以在streamB
中的某些更新到达之前(例如在启动期间)进行处理?
我建议从Table API is solving this issue的方式中汲取灵感。可能是临时表联接是您的正确选择,这会让您选择:处理时间还是事件时间?
Gaurav Kumar的提议中的两个都是实现of processing time Temporal Table joins,它们假设记录可以非常松散地连接,并且不必正确地计时。
如果必须正确定时streamA
和streamB
中的记录,那么就必须用一种或另一种方法来缓冲来自两个流的某些记录。具体方法有多种,具体取决于您要实现的semantic。决定了这一点之后,实际的实现并不困难,您可以从Table API联接运算符(org.apache.flink.table.runtime.join
模块中的flink-table-planner
包)中汲取灵感。
侧输入(您引用的)和/或输入选择仅是用于控制不必要的缓冲记录的数量的工具。您可以在没有它们的情况下实现有效的Flink作业,但是如果一个流明显超过另一个流(就事件时间而言,对于处理时间而言,它不是问题),则很难控制内存消耗。
答案 1 :(得分:1)
答案取决于您用来充实streamB
的{{1}}状态的大小
streamA
状态,那么您会将所有streamB的用户ID放置到每个任务管理器中。任务管理器上的每个任务将仅具有来自streamA的这些用户ID的子集。因此,streamB中的某些userId数据将永远不会使用,并且会浪费。因此,如果您认为streamB
状态的大小不足以真正影响您的工作,并且不占用大量内存来为状态管理留下更少的内存,则可以保留整个streamB
状态。这是您的#1。streamB
状态确实很大,并且会占用任务管理器的大量内存,则应考虑方法2。通过相同的ID标识两个流,以确保具有相同userID的元素完成相同的任务,然后可以使用托管状态来维护每个密钥streamB状态,并使用此托管状态来丰富streamA元素。