通俗易懂的理解spark的分布式计算

在什么是RDD中,通过将土豆的加工流程和wordcount进行了类比,如图所示:

image.png

image.png

我们来说一下二者的区别:

首先,Word Count 计算流图是一种抽象的流程图,而土豆工坊的流水线是可操作、可运行而又具体的执行步骤。然后,计算流图中的每一个元素,如 lineRDD、wordRDD,都是“虚”的数据集抽象,而流水线上各个环节不同形态的食材,比如一颗颗脏兮兮的土豆,都是“实实在在”的实物。

那么二者之间的关系,我们可以这样理解,把计算流图看作是“设计图纸”,那么流水线工艺其实就是“施工过程”。前者是设计层面、高屋建瓴的指导意见,而后者是执行层面、按部就班的实施过程。前者是后者的基石,而后者是前者的具化。

分布式的精髓就在于如何把抽象的计算流图,转化为实实在在的分布式计算任务,然后以并行计算的方式交付执行。

分布式计算的实现,离不开两个关键要素,一个是进程模型,另一个是分布式的环境部署。

进程模型

在 Spark 的应用开发中,任何一个应用程序的入口,都是带有 SparkSession 的 main 函数。SparkSession 包罗万象,它在提供 Spark 运行时上下文的同时(如调度系统、存储系统、内存管理、RPC 通信),也可以为开发者提供创建、转换、计算分布式数据集(如 RDD)的开发 API。

不过,在 Spark 分布式计算环境中,有且仅有一个 JVM 进程运行这样的 main 函数,这个特殊的 JVM 进程,在 Spark 中有个专门的术语,叫作“Driver”。

Driver 最核心的作用在于,解析用户代码、构建计算流图,然后将计算流图转化为分布式任务,并把任务分发给集群中的执行进程交付运行。换句话说,Driver 的角色是拆解任务、派活儿,而真正干活儿的“苦力”,是执行进程。在 Spark 的分布式环境中,这样的执行进程可以有一个或是多个,它们也有专门的术语,叫作“Executor”。

扫描二维码关注公众号,回复: 14242541 查看本文章

我把 Driver 和 Executor 的关系画成了一张图,你可以看看:

image.png 分布式计算的核心是任务调度,而分布式任务的调度与执行,仰仗的是 Driver 与 Executors 之间的通力合作。所以我们要厘清 Driver 与 Executors 的关系。

Driver 与 Executors:包工头与施工工人

简单来看,Driver 与 Executors 的关系,就像是工地上包工头与施工工人们之间的关系。包工头负责“揽活儿”,拿到设计图纸之后负责拆解任务,把二维平面图,细化成夯土、打地基、砌墙、浇筑钢筋混凝土等任务,然后再把任务派发给手下的工人。工人们认领到任务之后,相对独立地去完成各自的任务,仅在必要的时候进行沟通与协调。

其实不同的建筑任务之间,往往是存在依赖关系的,比如,砌墙一定是在地基打成之后才能施工,同理,浇筑钢筋混凝土也一定要等到砖墙砌成之后才能进行。因此,Driver 这个“包工头”的重要职责之一,就是合理有序地拆解并安排建筑任务。

再者,为了保证施工进度,Driver 除了分发任务之外,还需要定期与每个 Executor 进行沟通,及时获取他们的工作进展,从而协调整体的执行进度。

要履行上述一系列的职责,Driver 自然需要一些给力的帮手才行。在 Spark 的 Driver 进程中,DAGScheduler、TaskScheduler 和 SchedulerBackend 这三个对象通力合作,依次完成分布式任务调度的 3 个核心步骤,也就是:

  1. 根据用户代码构建计算流图;
  2. 根据计算流图拆解出分布式任务;
  3. 将分布式任务分发到 Executors 中去。

接收到任务之后,Executors 调用内部线程池,结合事先分配好的数据分片,并发地执行任务代码。对于一个完整的 RDD,每个 Executors 负责处理这个 RDD 的一个数据分片子集。这就好比是,对于工地上所有的砖头,甲、乙、丙三个工人分别认领其中的三分之一,然后拿来分别构筑东、西、北三面高墙。

举例说明:

image.png

首先,Driver 通过 take 这个 Action 算子,来触发执行先前构建好的计算流图。沿着计算流图的执行方向,也就是图中从上到下的方向,Driver 以 Shuffle 为边界创建、分发分布式任务。

而reduceByKey会执行shuffle操作。对于 reduceByKey 之前的所有操作,也就是 textFile、flatMap、filter、map 等,Driver 会把它们“捏合”成一份任务,然后一次性地把这份任务打包、分发给每一个 Executors。

三个 Executors 接收到任务之后,先是对任务进行解析,把任务拆解成 textFile、flatMap、filter、map 这 4 个步骤,然后分别对自己负责的数据分片进行处理。

为了方便说明,我们不妨假设并行度为 3,也就是原始数据文件 wikiOfSpark.txt 被切割成了 3 份,这样每个 Executors 刚好处理其中的一份。数据处理完毕之后,分片内容就从原来的 RDD[String]转换成了包含键值对的 RDD[(String, Int)],其中每个单词的计数都置位 1。此时 Executors 会及时地向 Driver 汇报自己的工作进展,从而方便 Driver 来统一协调大家下一步的工作。

这个时候,要继续进行后面的聚合计算,也就是计数操作,就必须进行刚刚说的 Shuffle 操作。在不同 Executors 完成单词的数据交换之后,Driver 继续创建并分发下一个阶段的任务,也就是按照单词做分组计数。

数据交换之后,所有相同的单词都分发到了相同的 Executors 上去,这个时候,各个 Executors 拿到 reduceByKey 的任务,只需要各自独立地去完成统计计数即可。完成计数之后,Executors 会把最终的计算结果统一返回给 Driver。

这就是spark的分布式基本运行流程。

猜你喜欢

转载自juejin.im/post/7106309495692722213
今日推荐