最近工作中开始用 Spark 了,完全是赶鸭子上架 0 基础。想着把自己的学习经历记录下,而且写点博客也对求职有帮助,就试着写了一篇,发现写这玩意确实很难啊,写了半天写得一塌糊涂。一方面是自己才疏学浅,刚毕业大学学的的东西也忘得差不多了;一方面是不太会写,写文章没有条理。
希望大家读完给点建议,挑挑错~ 面向的读者大概是 Spark 的使用者吧。
第一篇先讲讲函数式编程在 Spark 中的应用,因为理解函数式编程对理解 Spark 的一些原理还是很有帮助的。
函数式编程有两个比较重要的点,一个是无副作用,一个是 immutable 变量。
f(x)=x + 1
,对于这个函数,只要它的入参是 n,它的输出值永远都是 n+1 。因为这个函数不依赖任何外部的状态,只依赖入参。这一特性是 Spark 的 RDD 懒加载的理论基础。编写过一些 Spark 程序的人,应该了解 Spark 有两种对 RDD/dataset 的操作。一种是 Transform 类型,包括 map,mapPartition 等;一种是 action 类型,包括 count,reduce 等。
看下这个例子:
val sampleRdd = sc.makeRDD(Array(1, 2, 3, 4), 2)
val rdd2 = sampleRdd.filter((str) => str >= 2)
val count = rdd2.count()
spark 会等到调用 count()的时候才执行 filter(),这就是懒加载,只有用户真正需要结果的时候才执行全过程。所有 transform 类型的操作都是懒加载的,而 action 类型会触发 rdd 上的所有 transform 。懒加载可以让程序集中运行,节约资源,而且在 Spark 这样的分布式程序里可以节约大量的通信调度时间。
为什么函数式编程的无副作用的对懒加载相当重要呢,我们来看下面这个例子。
val xMinBefore = (x : Int) => {
System.currentTimeMillis / 1000 - 60 * x;
}
看到这个函数,它就是有副作用的,它的运行结果不仅依赖入参,还依赖于这个世界的时间,所以你在不同时间去执行这个函数,返回的值是不同的。对于这种有副作用的函数,我们自然不能使用懒加载,我们需要在调用的时候就得到结果,才能保证结果是我们需要的。而没有副作用的函数,我们可以在任何时候执行都得到一致的结果,自然可以在任何时候调用,也就保证了在最后加载的时候,输出的数据是不会有问题的。
保证了无副作用,Spark 的容错机制 lineage 才能起作用。回到上面那个例子
val sampleRdd = sc.makeRDD(Array(1, 2, 3, 4), 2)
val rdd2 = sampleRdd.filter((str) => str >= 2)
val count = rdd2.count()
rdd2 是在 sampleRdd 的基础上进行了 filter 操作,rdd2 实际上就是保留了一个对 sampleRdd 的引用,并记录了 filter 操作。rdd2 这样就有了和 samleRdd 的 lineage 。
lineage 的应用: 在执行第三行 count()时,rdd2 就会在 sampleRdd 的基础上执行 filter 操作。当执行 filter 的时候失败了,spark 就会往上追溯到上一个成功的 rdd,也就是 sampleRdd,再执行一把。可以使用这种方式容错的前提,就是 RDD 的 immutable 特性以及无副作用,它们保证在任何时候对一个 RDD 执行相同操作时,输出的结果永远是一样的。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.