有没有比我正在做的 time window 更好的方法来加入来自两个键控流的更新?

Is there a better way to joining updates from two keyed streams than time window I am doing?

所以我有一个 Flink 作业,它从 Kafka 接收消息并将创建两个 updateObject 事件,然后通过该作业发送。为简单起见,一个是增加事件,另一个是减少事件(因此消息进来,业务逻辑决定创建什么 updateObjects 以便正确更新状态)。

给个示例更新对象:

{
   "batchId":"b1",
   "type":"increase",
   "amount":1,
   "stateToUpdate":"stateAlpha",
   "stateToUpdateValue":null
}

然后将更新对象发送到作业的下游。 Stream 由一个值作为键控,该值将使 updateObject1 更新一种状态,而 updateObject2 通过该键更新另一种状态。在每个更新它们的状态之后,更新的值被放入 updateObject 的 stateToUpdateValue 并且每个都通过作业发送。

现在棘手的部分是最后一步。作业的最终输出是每条消息的 updateObjects 数组。可以想到的最好的想法是有一个 1 秒的翻滚时间 window 来收集 updateObjects,然后当它在 1 秒后触发时,它将检查其 window 中的所有内容并将它们配对具有相同的batchId,并将它们放入最终输出对象中,然后输出。显然,这并不是不能保证所有人都可以在 1 秒内完成 window,但它也会导致处理延迟,因为事情就在那里。

不能保证总是为每条消息创建两个 updateObjects,因为这在很大程度上是个案类型。并且由于 updateObjects 被分成不同的键控流,因为它们的键总是不同的,所以不可能是单个对象经过第一个键控状态并更新它,然后通过单个对象进行下一个键控状态并相应地更新对于每个。可以说,一旦键控发生,它们就不会被附加。

所以我想知道是否有人能想出更好的方法来做到这一点,因为我觉得肯定有。

你怀疑有更好的方法来做到这一点是正确的。

翻滚 windows 有两个问题:

  1. 对于通常不会花费那么长时间的事情,您平均需要等待半秒,最差需要等待一秒。
  2. 尽管您经常一直在等待,但这种基于 windows 的方法仍然很容易出错。即使一个批处理只有两个事件,即使它们在彼此相隔几毫秒内被处理,它们仍然可能落在 window 边界的相对两侧。这是因为 one-second-long window 将从 12:00:00.000 到 12:00:00:999,例如,您的活动可能会显示在 12:00:00 .999 和 12:00:01.001.

这是一个替代方案:

在管道的末端,re-key batchId 的流,然后使用 KeyedProcessFunction 将批次粘合在一起。

例如,当属于特定批次的每条记录到达时,将其附加到键控 ListState 对象。如果你确切地知道每个批次有多少条记录,那么你可以在 ValueState 中保留一个计数器,当批次完成时,你可以遍历列表并产生最终结果(并且不要忘记在你已经完成了)。或者您可以使用键控计时器等待特定的持续时间(相对于每批中第一条记录的到达),并在计时器触发时产生最终结果。

working with state and with process functions in the tutorials in Flink's documentation, and in the accompanying training exercises的例子。

或者,您可以使用 Flink SQL,使用 OVER windows。