原文引用 大专栏 https://www.dazhuanlan.com/2019/08/26/5d634faceb33c/
为了能够深入使用 Spark ,那么必须对 Spark 有更为深入的理解。整体的结构的把握会显得非常重要。整体结构在脑海中成型后,别如同脑海中有了一个 Spark 的地图,后续深入了解的每个模块都能在整体上找到对应的位置,开发对应功能也会更加了然于胸。 网上的结构图非常丰富,但是我觉得都太过抽象,缺少一些细节。我这里尝试做的就是帮助 Spark 新手读者能构建一个 Spark 的直观感受,同时也是我自己知识的一个整理。
小Demo
下面是一段简单的 demo 进程。首先从数据源中取数,这里是从文本中读取数据。然后进行一系列的转换,最终得到自己想要的结果。
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("Spark WordCount").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("your/file/path");
lines.flatMap((String s) -> Arrays.asList(s.split(" ")).iterator())
.mapToPair((String word) -> new Tuple2<>(word, 1))
.reduceByKey((Integer v1, Integer v2) -> v1 + v2)
.foreach((Tuple2<String, Integer> p) -> System.out.println(p._1 + " : " + p._2));
sc.close();
}
这里代码内部有一个函数式计算 中非常重要的内容,流计算。曾经看过一个对流计算的形容,大意为流计算如同一个高高挂起的一桶水。每当增加一个计算,就如同在下方放置了个工具(比如滤网 filter),水桶依然高高挂起没有任何动作,直到触发一个按钮,那桶水倾倒处理,经过一个个计算节点,最后达到终点。
在下面的代码中,这里我们进入到最后的 foreache
方法,发现了触发倒水的按钮runJob。
def foreach(f: T => Unit): Unit = withScope {
val cleanF = sc.clean(f)
sc.runJob(this, (iter: Iterator[T]) => iter.foreach(cleanF))
}
重要概念
- RDD 全称是Resilient Distributed Datasets。RDD 中包含由 N 个 partition
- partition是计算的最小单元。每个 partition 都是相互独立的。
- 一个 RDD 和另外一个 RDD 的依赖关系分为宽依赖和窄依赖
- 宽依赖。一个 RDD_a给一个计算得到 RDD_b。如果b 中的一块 partition 数据时计算a 中 N 块 partition 得出的。那么就是 RDD_b宽依赖 RDD_a
- 窄依赖。如果b 中的一块 partition 数据时计算a 中 1 块 partition 得出的。那么就是 RDD_b窄依赖 RDD_a
- 窄依赖可以在同一台机器上完成。
- 宽依赖必须进行 shuffle,将计算需要的数据放到一起计算才行。
执行你的进程
首先,记住经过你的一系列计算后,你拿到的是包含了一系列的计算过程的RDD。当把这个RDD提交给 spark 后,spark 会做一下操作来得到最终结果。
- 解析出 RDD 中每一步的依赖关系。却别出子 RDD 对于父RDD 是,同一个partition 依赖(窄依赖)还是跨多个 partition(宽依赖)
- 按照宽依赖来切割 stage。需要进行shuffle 将不同机器不同partition的数据进行整理,以便同一key的数据发到同一台机器进行计算。
- 创建 Task。Task 只有两种,一种是 ShuffleMapTask,就是做 shuffle 的。另外一种是 ResultTask,就是计算最终结果的 Task。同样可以知道一个 Task 的数据来源也有两个,一个是文本,数据库之类的数据源。还有一个就是 上一个ShuffleMapTask 的结果。
- 以上操作都是在 Driver 端完成,下面Driver需要将安排好的 Task 提交到 Executor上执行。 当然,在提交之前首先要Master申请executor资源,获得资源后才能提交。
- Executor拿到 Task 后,便开始计算。这里就是上面水桶比喻中的倒水操作。executor 计算好之后,告知 Driver 计算结束。Driver 会将余下的 Task 依次提交,直到得出最终结果。至此你的计算就在 Spark 的帮助下完成了。
- 需要注意的是,Executor 返回个 Driver 的计算成功与否的结果,并非数据的计算结果。每一个 ShuffleMapTask的计算结果都是由 Spark 管理的,下一个 Task 需要上一个 Task 的结果时候,只需找 Spark 获取即可。
后面的话
从宏观上看大数据本质上还是非常朴素的,但是由于实现细节比较复杂,导致了很多门槛从而难以下手。当有了一个全局的感受后就会容易很多。