在数据流上的操作可以是无状态的(stateless),也可以是有状态的(stateful),无状态的操作不会维持内部状态,即处理事件时无需依赖已处理过的事件,也不保存历史事件。由于事件处理互不影响而且与事件到来的时间无关,无状态的操作很容易并行化。此外,如果发生故障,无状态的算子可以很容易地重启并恢复工作。相反,有状态的算子可能需要维护之前接收到的事件信息,它们的状态会根据新来的事件更新,并用于未来事件的处理逻辑中。有状态的流处理应用在并行化和容错方面会更具挑战性。
有状态算子同时使用传入的事件和内部状态来计算输出。由于流式算子处理的都是潜在无穷无尽的数据,所以必须小心避免内部状态无限增长。为了限制状态大小,算子通常都会只保留到目前为止所见事件的摘要或概览。这种摘要可能是一个数量值,一个累加值,一个对至今为止全部事件的抽样,一个窗口缓冲或是一个保留了应用运行过程中某些有价值信息的自定义数据结构。
不难想象,支持有状态算子会面临很多实现上的挑战。有状态算子需要保证状态可以恢复,并且即使出现故障也要确保结果正确。
至多一次(At Most Once)它表示每个事件至多被处理一次。
任务发生故障时最简单的措施就是既不恢复丢失的状态,也不重放丢失的事件。换句话说,事件可以随意丢弃,不保证结果的正确性。
至少一次(At Least Once)它表示所有事件最终都会被处理,虽然有些可能会处理多次。
对大多数应用而言,用户期望是不丢事件。如果最终结果的正确性仅依赖信息的完整度,那重复处理或者可以接受。例如,确认某个事件是否出现过,就可以用至少一次保证正确的结果。它最坏的结果也无非就是重复判断了几次。但如果要计算某个事件出现的次数,至少一次可能就会返回错误的结果。
为了确保至少一次语义的正确性,需要想办法从源头或者缓冲区中重放事件。持久化事件日志会将所有事件写入永久存储,这样在任务故障时就可以重放它们。实现该功能的另一个方法是采用记录确认(ack)。将所有事件存在缓冲区中,直到处理管道中所有任务都确认某个事件已经处理完毕才会将事件丢弃。
精确一次(Exactly Once)它表示不但没有事件丢失,而且每个事件对于内部状态的更新都只有一次。
精确一次是最严格,也是最难实现的一类保障。本质上,精确一次语义意味着应用总会提供正确的结果,就如同故障从未发生过一样。
精确一次同样需要事件重放机制。此外,流处理引擎需要确保内部状态的一致性,即在故障恢复后,计算引擎需要知道某个事件对应的更新是否已经反映到状态上。事务性更新是实现该目标的一个方法,但它可能会带来极大的性能开销。Flink采用了轻量级检查点机制(Checkpoint)来实现精确一次的结果保障。(flink系列后续会有文章重点分析,这里先知道有这么一回事即可。)
上面提到的“结果保障”,指的都是流处理引擎内部状态的一致性。也就是说,我们关注故障恢复后应用代码能够看到的状态值。请注意,保证应用状态的一致性和保证输出的一致性并不是一回事。一旦数据从Sink中写出,除非目标系统支持事务,否则最终结果的正确性难以保证。
窗口操作官方有篇关于窗口介绍的博客:https://flink.apache.org/news/2015/12/04/Introducing-windows.html
windows相关的文档:https://ci.apache.org/projects/flink/flink-docs-release-1.11/dev/stream/operators/windows.html#windows
Windows are at the heart of processing infinite streams. Windows split the stream into “buckets” of finite size, over which we can apply computations.
转换操作(Transformation)可能每处理一个事件就产生结果并(可能)更新状态。然而,有些操作必须收集并缓冲记录才能计算结果,例如流式Join或者求中位数的聚合(Aggregation)。为了在无界数据流上高效地执行这些操作,必须对操作所维持的数据量加以限制。下面将讨论支持该项功能的窗口操作。
此外,有了窗口操作,就能在数据流上完成一些具有实际语义价值的计算。比如说,最近几分钟某路口的车流量、某路口每10分钟的车流量等。
窗口操作会持续创建一些称为桶的有限事件集合,并允许我们基于这些有限集进行计算。事件通常会根据其时间或其他数据属性分配到不同桶中。为了准确定义窗口算子语义,我们需要决定事件如何分配到桶中以及窗口用怎样的频率产生结果。窗口的行为是由一系列策略定义的,这些窗口策略决定了什么时间创建桶,事件如何分配到桶中以及桶内数据什么时间参与计算。其中参与计算的决策会根据触发条件判定,当触发条件满足时,桶内数据会发送给一个计算函数,由它来对桶内的元素应用计算逻辑。这些计算函数可以是某些聚合(例如计数(count)、求和(sum)、最大值(max)、最小值(min)、平均值(avg)等),也可以是自定义操作。策略的指定可以基于时间(例如“最近5秒钟接收的事件”)、数量(例如“最新100个事件”)或其他数据属性。
接下来就重点介绍几种常见的窗口语义。
滚动窗口