《Streaming Systems》第三章的关注点是水印(Watermark)。作者在第二章中简单描述过水印是流处理过程中数据完整性的度量,但囿于篇幅,没有给出水印这个概念的准确定义以及如何应用。因此作者在第三章中针对水印这个重要概念做了详细的描述,包括水印的创建过程、Pipeline的各个阶段(Stage)迭代演进等内容,并在文章的最后给出了水印的工程应用。
在上文提到过,水印是数据完整性的度量,也就是说,水印决定了流处理系统(以下用“系统”代指)何时关闭事件时间窗口(event-time window),不再接收任何迟到的数据(late data),开始计算输出结果。这样的描述很容易理解,但不够精确,因此作者基于任意一个事件都包含属于自己的逻辑时间戳的假设和事件消息在流处理系统中可以被划分为“in-flight”和“completed”两个状态,定义水印为:
The watermark is a monotonically increasing timestamp of the oldest work not yet completed.
这个定义里包含了两个核心概念:
水印的创建分为两个部分:完美水印和推测水印(perfect or heuristic)。完美水印表示窗口会一直等待着所有数据的到齐才会计算输出结果,而推测水印则是在有可能丢失部分数据的情况减少系统的延迟。
完美水印是流处理过程中系统对事件时间完整性的严格保证,保证系统在计算输出结果之后不会再有晚于此刻的事件时间存在。通过定义可知,实现完美水印前提是系统必须对整个无界数据集有着充分的了解。有如下例子可以创建完美水印:
推测水印是一种假设,假设在水印结束之后,流处理系统不会再接受到晚于这个事件时间的事件,也就是意味着可以丢弃迟到的数据(late data)。因此对于推测水印的使用需要谨慎考量数据的准确性是否可以被放弃。有如下例子可以创建推测水印:
对于性能和准确性,没有one-size-fits-all的解法,因此作者也给出建议,让使用者根据实际情况合理选用不同类型的水印。而对于水印的概念本身,作者指出它的意义在于减少了系统对无界数据集中数据完整性的理解复杂性。
前文关于水印的内容都局限在pipeline的一个孤立阶段,现在需要将水印放置在整个pipeline过程中通盘考虑。Pipeline本身是可以划分为一系列单独的阶段(stage)的,就像MapReduce可以简单的分为Map阶段和Reduce阶段,因此可以在Pipeline的每一个单独的阶段(stage)定义属于自己的水印。按照这个理解,在Pipeline中的水印可以被划分为:
通过将每个阶段(stage)的水印区分为输入和输出水印,系统可以获得整个Pipeline中每一个阶段(stage)的延迟(也就是数据处理需要花费的时间),从而更好的缓存(buffer)数据以及追踪系统级别的数据延迟。
在Pipeline每一个阶段(Stage)的结束时,系统会选择一个时间戳作为这个Stage,或者是窗口(window)结束的标志,也就是输出时间戳。对于输出时间戳的选择,一般而言使用者会有如下选项:
ps:对于滑动窗口(sliding windows),情况会发生一点变化,因为窗口之间会发生重叠导致使用元素本身的时间戳进行计算的话,会导致不可避免地延迟,因此需要系统本身去保证N+1的窗口的输出时间戳永远大于N的窗口的输出时间戳。
在第三章中为了水印概念的完整性,作者还提出了百分比水印(Percentile Watermarks)和处理时间水印(Processing-Time Watermarks )两个概念作为补充。
这部分在文章讲的不是那么详细,暂且不表。有机会的话,我会在《Streaming Systematic》第一大部分The Beam Model结束后专门写一篇文章详细比较Spark和Flink的最新版本对The Beam Model的实现。