有没有比我正在做的 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 有两个问题:
- 对于通常不会花费那么长时间的事情,您平均需要等待半秒,最差需要等待一秒。
- 尽管您经常一直在等待,但这种基于 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。
所以我有一个 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 有两个问题:
- 对于通常不会花费那么长时间的事情,您平均需要等待半秒,最差需要等待一秒。
- 尽管您经常一直在等待,但这种基于 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。