从接触面向过程语言开始,使用控制流编程的概念已经是司空见惯。html
if (condition) { // do something } else { // do something else }
分支和循环是最多见的控制流形式。因为控制条件的存在,总有一部分代码片断会执行,另外一部分不会执行。java
在控制流中,想要进行数据传递,最关键的是借助于变量保存中间状态。所以,控制流编程看起来是将数据嵌套在控制流内的编程方式。python
使用变量保存程序状态有个很大的优点。经过变量缓存,能够将编程任务划分为不一样的阶段,每一个阶段只须要完成一部分功能子逻辑便可,这大大下降了复杂流程的思惟成本。编程
但同时,也有一个比较大的劣势,就是在分布式处理环境下,中间状态的维护一直是一个很繁琐的问题。这从另外一个方面加大了程序设计的成本。缓存
而数据流编程的概念最初能够探寻到函数式编程语言,以及灵感源于此的FlumeJava类系统(如Spark、Flink等)的编程API。框架
rdd.map(lambda).filter(lambda).reduce(lambda);
这种相似管道流水线形式的编程接口,每次处理的数据是列表形式的(LISP)。固然,这些列表放在分布式环境下换了一个新的名词——分布式数据集(RDD/DataSet)。编程语言
数据流编程最大的特色是抽象了丰富的算子,经过UDF为算子指定用户处理逻辑。所以,数据流编程其实蕴含了控制流嵌套在数据流内的编程方式。分布式
使用数据流编程最大的优点就是无需使用变量维护计算中间状态,另外基本的列表数据格式自然知足分布式数据存储的要求。这也是函数式语言在自我宣传时比较注重的一个优点:对并行计算支持得更好。函数式编程
不过,数据流编程的方式也并非完美。因为事先规划好的流水线结构,致使了数据处理没法自主地选择流水线分支进行处理。因此,有时候看似很简单的控制逻辑,使用数据流表达时就显得比较繁琐。函数
例如:下面的控制流程使用控制流编程很好表达。
if (arg > MAX) { vertices = vertices.map(lambda); } else { vertices = vertices.filter(lambda); } return vertices;
这里的参数arg可能来源于用户输入,或者Spark/Flink driver提供的变量。这种使用driver的单机控制流全局统筹的方式好像是解决了数据流选择选择流水线管道的目的,可是实际上这是经过从新提交新任务的方式完成的。即条件为真时,才会提交true分支内的计算任务,不然提交false分支的计算任务。
若是不借助于driver,该如何表达相似的分支控制流程呢?
假定参数arg的类型也是分布式数据集类型DataSet<Integer>,它可能来源于上游流水线的中间结果,那么表达分支控制流计算可能须要以下相似方式:
// 条件数据集 DataSet<Boolean> condition = arg.map(v -> v > MAX); // 数据集 true/false 分离 DataSet<Tuple2<Vertex, Boolean>> labelVs = vertices.join(condition); DataSet<Vertex> trueVs = labelVs.filter(v -> v.f1).map(v -> v.f0); DataSet<Vertex> falseVs = labelVs.filter(v -> !v.f1).map(v -> v.f0); // 各自分支处理 trueVs = trueVs.map(); falseVs = falseVs.filter(); return trueVs.union(falseVs);
这里经过将参数DataSet与输入数据集vertices作join,而后分离(按条件true/false filter)出两个新的数据集trueVs和falseVs。当条件为true时,trueVs就是原始数据集vertices,而falseVs为空数据集,反之则反。而后后续只要分别对这两个数据集作相应的处理,最后把处理结果union合并起来就达到了目的。
经过这样的方式,其实是同时执行了条件的true和false的分支逻辑,只不过任什么时候候总有一个分支的流水线上的数据集为空罢了。
经过前面的讨论,能够获得一些比较明显的结论:
在计算编程语言设计领域,对控制流和数据流的讨论不绝于耳。如何让开发者更好的操纵这两类概念也在不断地探索,要否则也不会出现面向过程和函数式编程等各类编程范式。
而目前主流的计算系统,如Flink、Spark等,基本上处于使用driver的概念表达控制流,使用算子链接数据流这样的模式。不过这都是创建在driver经过全局collect操做,将数据集的数据拉取到driver基础之上的。本质上是driver根据条件分支的运行时结果,从新提交任务而已,这称不上一个精彩的设计。由于,它并无作到让数据流具有自主选择流水线的能力。
那如何让数据流具有自主选择流水线的能力呢?说白了,自主选择流水线,本质上是拥有任务运行时修改任务执行计划的能力,也就是所谓的动态DAG。Ray的设计中,函数是基本的任务调度单元,而非将UDF链接起来的DAG,或许这种底层的任务抽象能力对于表达动态DAG的能力具备更大的优点。
详细了解Ray的设计,能够参考文章:高性能分布式执行框架——Ray
个人博客即将同步至腾讯云+社区,邀请你们一同入驻。