由Dataflow模型聊Flink和Spark

文章如约而至。这是一篇对Dataflow模型的回顾和小小的总结,并以Spark和Flink为例,简单地描述Dataflow模型是如何影响Spark和Flink对于流的设计和实现。对Dataflow模型有疑惑的读者可以先阅读我写的前四篇文章,再回过头来读这篇文章。

Dataflow模型的诞生

Dataflow模型(或者说Beam模型)旨在建立一套准确可靠的关于流处理的解决方案。在Dataflow模型提出以前,流处理常被认为是一种不可靠但低延迟的处理方式,需要配合类似于MapReduce的准确但高延迟的批处理框架才能得到一个可靠的结果,这就是著名的Lambda架构。这种架构给应用带来了很多的麻烦,例如引入多套组件导致系统的复杂性、可维护性提高。因此Lambda架构遭到很多开发者的炮轰,并试图设计一套统一批流的架构减少这种复杂性。Spark 1.X的Mirco-Batch模型就尝试从批处理的角度处理流数据,将不间断的流数据切分为一个个微小的批处理块,从而可以使用批处理的transform操作处理数据。还有Jay提出的Kappa架构,使用类似于Kafka的日志型消息存储作为中间件,从流处理的角度处理批处理。在工程师的不断努力和尝试下,Dataflow模型孕育而生。

起初,Dataflow模型是为了解决Google的广告变现问题而设计的。因为广告主需要实时的知道自己投放的广告播放、观看情况等指标从而更好的进行决策,但是批处理框架Mapreduce、Spark等无法满足时延的要求(因为它们需要等待所有的数据成为一个批次后才会开始处理),(当时)新生的流处理框架Aurora、Niagara等还没经受大规模生产环境的考验,饱经考验的流处理框架Storm、Samza却没有“恰好一次”的准确性保障(在广告投放时,如果播放量算多一次,意味广告主的亏损,导致对平台的不信任,而少算一次则是平台的亏损,平台方很难接受),DStreaming(Spark1.X)无法处理事件时间,只有基于记录数或基于数据处理时间的窗口,Lambda架构过于复杂且可维护性低,最契合的Flink在当时并未成熟。最后Google只能基于MillWheel重新审视流的概念设计出Dataflow模型和Google Cloud Dataflow框架,并最终影响了Spark 2.x和Flink的发展,也促使了Apache Beam项目的开源。

Dataflow模型回顾

Dataflow模型从流处理的角度重新审视数据处理过程,将批和流处理的数据抽象成数据集的概念,并将数据集划分为无界数据集和有界数据集,认为流处理是批处理的超集。模型定义了时间域(time domain)的概念,将时间明确的区分为事件时间(event-time)和处理时间(process-time),给出构建一个正确、稳定、低时延的流处理系统所会面临的四个问题及其解决办法:

  • 计算的结果是什么(What results are calculated)?
    通过transformations操作
  • 在事件时间中的哪个位置计算结果(Where in event time are results calculated)?
    使用窗口(windowing)的概念
  • 在处理时间中的哪个时刻触发计算结果(When in processing time are results materialized)?
    使用triggers + watermarks进行触发计算
  • 如何修正结果(How do refinements of results relate)?
    通过accumulation的类型修正结果数据

核心概念:

  • 事件时间(Event time)和处理时间(processing time)
    流处理中最重要的问题是事件发生的时间(事件时间)和处理系统观测到的时间(处理时间)存在延迟。
  • 窗口(Windowing)
    为了合理地计算无界数据集地结果,所以需要沿时间边界切分数据集(也就是窗口)。
  • 触发器(Triggers)
    触发器是一种表示处理过程中遇上某种特殊情况时,此刻的输出结果可以是精确的,有意义的机制。
  • 水印(Watermarks)
    水印是针对事件时间的概念,提供了一种事件时间相对于处理时间是乱序的系统中合理推测无界数据集里数据完整性的工具。
  • 累计类型(Accumulation)
    累计类型是处理单个窗口的输出数据是如何随着流处理的进程而发生变化的。

Dataflow模型的应用

现在让我们使用Dataflow模型的四个问题和五个概念,抛开具体的工程细节,重新审视Spark和Flink的设计。(暂且不论已经过时的Dstream,只关注以Structured Streaming为主的Spark 2.X是如何实现Dataflow模型的。)

时间域

Spark和Flink都在其官方文档中提到了事件时间和处理时间,Flink还进一步将进入时间(Ingress Time)从事件时间抽离出来。

处理时间

Flink:Processing time refers to the system time of the machine that is executing the respective operation.

Spark:the time when the record is received at the streaming application.

事件时间

Flink:Event time is the time that each individual event occurred on its producing device.

Spark:Event time is the idea of processing data based on timestamps inserted into each record at the source

进入时间

Flink:Ingestion time is the time that events enter Flink.

从官方定义上看,Spark的对于处理时间的定义更像是Flink对进入时间的定义,Spark没有明确的区分应用在处理过程中处理时间的变化,而Flink更接近于Dataflow模型,通过进入时间和处理时间区分了事件流在整个流处理过程中转换的变化。这一点的变化影响了Spark和Flink后面关于API的设计,相较于Flink的灵活,Spark就显得比较死板了。

计算的结果是什么(What results are calculated)?

Flink:Operators transform one or more DataStreams into a new DataStream.

Spark:Structured Streaming maintains the same concept of transformations and actions(that we have seen in the DStream.)

Spark和Flink都会使用类似于transform操作实现Pipeline。Spark是在已经成熟的DataFrame Transformations上做了进一步扩展,而Flink使用的是Operators的Transformations操作,两者大同小异。相较于Spark的SQL引擎的成熟,Flink还有不少的提升空间。

在事件时间中的哪个位置计算结果(Where in event time are results calculated)?

在Dataflow模型中,有四种类型的窗口:Tumbling WindowsSliding WindowsSession WindowsCustom Windows。而Spark实现了基于事件时间的Tumbling WindowsSliding Windows,而Session Windows是通过MapGroupsWithStateflatMapGroupsWithState去实现的(目前在Spark2.4只支持Scala和Java),Custom Windows则完全不存在。Flink提供了更为灵活的window窗口方式,不仅仅提供了Tumbling WindowsSliding WindowsSession Windows,而且通过window assigner实现了Custom Windows,在文档中注意到,Flink还进一步区分了Group操作和非Group操作所使用的window。在Window的层面,Flink设计的要比Spark优秀的多,特别是Session WindowSession Window最好的实现方式是通过Declarative API(告诉框架要做什么,而不是告诉框架怎么做),而不是使用MapGroupsWithStateflatMapGroupsWithState增加复杂度。虽然大部分使用场景使用Tumbling WindowsSliding WindowsSession Windows也绰绰有余了,但是对于Spark而言,Custom Windows的缺失依旧限制了它在一些特殊场景的使用。

在处理时间中的哪个时刻触发计算结果(When in processing time are results materialized)?

触发器(Triggers)和水印(Watermarks)是保证流处理准确性的核心。

Flink:A Trigger determines when a window (as formed by the window assigner) is ready to be processed by the window function.

Spark:triggers define when data is output

触发器是通过外部条件触发结果的计算。在Dataflow模型中,触发器有很多种。在Spark里仅有两种类型的触发器,输入数据的完成度和基于处理时间间隔,但是不支持触发组合以及使用水印触发计算,后续有计划添加新的触发器类型。而Flink触发器种类更多,提供了onElementonEventTimeonProcessingTimeonMerge触发器,但是也不支持触发器组合。

Flink:The mechanism in Flink to measure progress in event time is watermarks

Spark:a watermark is an amount of time following a given event or set of events after which we do not expect to see any more data from that time.

水印用来衡量数据完整性,解决迟到数据的问题。Spark对于水印的理解只是(事件时间-迟到的时间间隔)>计算开始时间,也就是所谓的完美水印,而Flink的水印设计直接来源于Dataflow模型。
就大部分场景而言,Spark和Flink对于触发器和水印的实现已经完全满足了要求,没有什么好说的。

如何修正结果(How do refinements of results relate)?

这是一个有趣的话题,Dataflow提供了三种对数据结果的处理方式:append、update和complete,而恰巧Spark的Output Sink也提供了这三种数据结果的处理方式,Flink的官网对于输出结果只有append和complete模式,但是Sink的丰富度,Flink要比Spark强很多。

结论

Spark虽然已经开始引入了Dataflow模型,但是囿于自身的设计(Spark2.3才开始引入Continuous Processing),并没有完整的实现Dataflow模型,而且也没有提供足够灵活的API给开发者,而Flink应该是开源框架里实现Dataflow模型最完整的。虽说在理论模型上Flink远胜Spark,但是相对于Spark周边生态圈的完善(在Github搜索Spark,可以找到57,042个repository,而Flink只有2,551个repository),如果不是对于流式计算有着非常复杂的需求,一般情况还是会选择Spark。如果对流式计算有着非常高的要求,Flink将会是你的首选。

Dataflow模型的意义

Dataflow模型,我认为最大的意义在于从更高的维度重新审视了数据处理框架和分布式系统,正如书中所述:

At the end of the day, there is no stream, no table, no difference on batch or streaming, there is only simply data and the logic to process the data, that’s all.

参考文章:

1 、《Streaming Systems》

2、Flink官方文档:Stateful Computations over Data Streams

3、Spark官方文档:Unified Analytics Engine for Big Data

4、《Spark:The Definitive Guide》

关注微信公众号:鸿的笔记,获得最新文章

    原文作者:chauncy
    原文地址: https://zhuanlan.zhihu.com/p/61355244
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞