作者:刘旭晖 Raymond 转载请注明出处
Email:colorant at 163.com
BLOG:http://blog.csdn.net/colorant/
基于这种无边界数据集的特性,在Dataflow模型中,数据的处理过程被概括为以下4个方面的问题的解决:
– What results are being computed. : 计算逻辑是什么
– Where in event time they are being computed. : 计算什么时候(事件时间)的数据
– When in processing time they are materialized. : 在什么时候(处理时间)进行计算
– How earlier results relateto later refinements. : 后续数据的处理结果如何影响之前的处理结果
清晰的定义这些问题,并针对性的在模型框架层面加以解决,正是Dataflow区别于其它流式计算模型的核心关键所在。通常的流式计算框架往往模糊或者无法有效的区别对待数据的事件时间和处理时间,对于第4个问题,也可能缺乏直接的支持。这些问题通常需要开发人员自行在代码业务逻辑上想办法解决,因而也就加大了这类数据处理业务的开发难度,甚至成为一个不可能完成的任务。
而更重要的是,针对同一或类似数据集,各种数据处理需求,其核心计算逻辑往往可能是一致的,比如计算活跃用户数,核心计算逻辑就是一个去重逻辑。 但是根据应用目标场景,统计口径可能各有不同,比如可能要求计算过去一个小时的活跃用户,也可能是计算全天的累计的活跃用户,可能基于实际时间计算也可能基于数据采集时间计算,可能要求更新历史数据(有数据晚到),也可能处于效率,性能考虑,直接放弃晚到的数据。 Dataflow计算模型的目标是把上述4方面的问题,用明确的语意清晰的拆分出来,更好的模块化,快速适应各种业务逻辑开发需求。
例如在 https://cloud.google.com/dataflow/blog/dataflow-beam-and-spark-comparison 一文中,就用实际的例子比较了dataflow和spark在处理这类数据业务逻辑时,所需要进行的开发工作,总体的意思就是用dataflow模型开发,代码更简洁更容易理解,开发效率更高,维护成本更低。不过,需要注意的是,spark2.0的structure streaming API也引入了和Dataflow类似的模型思想,这篇文章里的很多比较已经不成立。
== 实现 ==
那么Dataflow是如何解决上面4方面的问题的呢,基本上,是通过构建以下三个核心功能模型来做到的:
=== 窗口模型 ===
为了在计算框架级别实现基于事件时间的窗口模型,Dataflow系统中,将常见的流式计算框架中的[key,value]两元组tuple形式的信息数据,变换成了[key,value, event time, window ]这样的四元组模型,event time的引入原因显而易见,必须要有相关载体承载这个信息(否则只能基于process time/batch time 划分窗口),而window窗口标识信息的引入,个人认为,很重要的一个原因是要支持Session类型的窗口模型,而同时,要将流式和增量更新的支持融合进窗口的概念中,也势必需要在数据中引入这样一个显示的窗口信息(否则,通常的做法就只能是用micro batch分组数据的方式,隐式的标识数据的窗口属性)
在消息的四元组数据结构基础上,Dataflow通过提供对消息进行窗口赋值,窗口合并,按key分组,按窗口分组等原子功能操作,来实现各种窗口模型。
=== 触发模型 ===
多数的基于Process time的固定或滑动窗口模型,并没有显示的窗口计算结果触发这样一个概念的定义,因为不太需要,窗口的边界时间点,也就是触发结果输出的时间点。而对于Dataflow来说,因为事件时间和处理时间的延迟,以及框架需要正确处理无序数据的需求,使得判断窗口的边界,触发计算和结果的输出变得困难起来。在这一点上,Dataflow部分借用了底层Millwheel提供的Low watermark低水位这样一个概念来解决窗口边界的判断问题,当低水位对应的时间点超过设定的时间窗口边界时间点时,完成窗口的计算和结果输出。但是,低水位的概念理论上虽然是OK的,在实际场景中,通常是一个概率模型,并不能完全保证准确的判断事件时间的延迟情况,而且有很多场合对窗口边界的判断,用户自己有自己的需求。
因此,Dataflow提供了可自定义的窗口触发模型,可以使用低水位做触发,也可以使用比如:定时触发,计数触发,计量触发,模式匹配触发或其它外部触发源,甚至各种触发条件的逻辑运算组合等不同等机制来应对可能的需求。
=== 增量更新 ===
当窗口被触发以后,对于后续晚到的数据,对已经触发过的窗口,如何处理,Dataflow在框架层面也提供了直接的支持,基本上包括三种策略:
== 相关研究,项目等 ==
=== spark 2.0 ===
Spark 2.0版本,新增的structured streaming API,针对原先的streaming编程接口DStream的问题进行了改进,Dstream的问题包括:
通过Structured Streaming API,Spark一方面支持了和Dataflow类似的概念,如Event time based的窗口策略,自定义的触发逻辑,对输出(sink)模块的更新模式(追加,全量覆盖,更新)的builtin支持,更加统一的处理无边界数据和有边界数据等。
总体看来,Spark 2.0的structured streaming 模型和Dataflow有异曲同工之处,设计的目标看起来很远大,甚至给出了一份功能比较表格来证明其优越性
不过在2.0的版本所支持的类Dataflow模型的功能还相对简单,比如session window,water flow等概念都还需要在2.1或者后续的版本中保证,也还不支持输出的更新模式,追加模式更新只能支持无聚合操作的场景,还有各种功能还停留在设想阶段,对于join等操作还有各种各样的限制等等,这些部分和dataflow业已实现的功能还有较大的差距。
对于exactly once发送的保障,spark2.0要求外部数据源具备offset定位的能力,再加上snapshot等机制来实现,而dataflow是通过对消息在框架内部进行持久化来实现replay,不依赖外部数据源的能力。
另外,个人理解像 prefix integrity, Transactional sink等概念,实际上是对上下游读写接口的一个封装,帮用户实现了一些业务逻辑(比如prefix integrity 的实现依托于于per key有序性的保证,这是由外部source源提供的保障,比如 file/kafka等;而Transactional sinks等则是比如对jdbc接口逻辑的封装),整体上偏外围功能一点,用这些特性来和其它框架比较不一定客观,因为设计理念不太不一样。Dataflow的模型设计中,用户能更加细化的定义每个环节的步骤和设置,所以不会把一些逻辑替用户实现,更多的是以模块化的方式,留给用户去自己选择,而Structured steaming则把很多事情包办了,定制的余地较小,灵活性应该会差一些,不过这也给程序的自动优化带来了一些便利。当然,这是我个人初步粗浅的理解,不见得准确。
=== beam ===
Beam http://beam.incubator.apache.org/ 是一个由谷歌发起的apache 项目,目前还处于incubator状态,基本来说就是实现dataflow编程模型的SDK项目,目标是提供一个high level的统一API编程接口,后端的执行引擎计划对接spark/flink/cloud dataflow。目前的编程语言支持Java,计划加入python。这个项目的前景如何,不太好说,单就适配各个后端的角度来说,就spark后端来说,在spark 1.x时代,这种high level的编程模型抽象是对spark编程模型的一种add on,有一定的附加价值,但是按照spark 2.0 structured streaming的发展路线来说,这一层抽象就稍微显得有些多余了。而基于Java的语法,在表达的简洁性上,相比scala也会带来一些额外的代价。
== 参考资料 ==