Spark: Master Workder
Yarn: ResourceManager (ApplicationMaster) NodeManager Container(用于可插拔计算框架)
Spark 有三大数据结构: RDD,广播变量,累加器
RDD: Resilient Distributed Dataset 弹性分布式数据集
描述:
是Spark计算的基石,为用户屏蔽了底层对数据的复杂抽象和处理,为用户提供了一组方便的数据转换与求值方法
特征: 不可变,可分区,弹性
不可变:生成新的RDD,而不是对原有的RDD改变
可分区:可并行运行
弹性:
存储的弹性:内存与磁盘的自动切换
容错的弹性:数据丢失可以自动恢复(数据丢失则查找上一个RDD的数据)
计算的弹性:计算出错重试机制(mapreduce默认重试4次)
分片的弹性:根据需要重新分片
RDD做了什么:
RDD的创建,RDD的转换,RDD的缓存,RDD的行动,RDD的输出
RDD是懒执行,主要两大类是RDD的转换和RDD的行动。RDD的行动算子不调用就不会执行前面的步骤。调用一次则从头开始计算一次,如果调用的多可以用持久化。
RDD的创建:
方式:
1 从内存中的对象集合中创建(也称并行化一个集合):
val config: SparkConf = new SparkConf().setMaster("local[*]").setAppName("Hello")
val sc = new SparkContext(config)
sc.parallelize(1 to 10)
sc.makeRDD(1 to 10)
2 从外部存储创建(eg:HDFS); sc.textFile(inputPath)
3 从其他RDD转换
从集合中创建有两种方式:
parallelize(真的实现) 和 makeRDD
由外部存储系统的数据集创建,包括本地的文件系统,HDFS,Cassandra,HBase等
转换和动作:
转换(transformation惰性):从现有RDD生成新的RDD
动作(action立刻):触发对RDD的计算并对计算结果执行某种操作,要么返回用户,要么保存到外部存储器
按键来为键值对RDD仅限聚合操作的三个主要转换函数:reduceByKey(),foldByKey(),aggregateByKey()
持久化:
tuples.cache(),大规模作业来说可以节省客观的时间,但只能由同一应用的作业来读取。若executor没有足够内存来存储RDD分区,计算不会失败,会根据需要重新计算分区
saveAsTextFile(),saveAsHadoopFile()可以存储到外部存储器,被多个应用使用
提供不同级别的持久化:
persist()并指定StorageLevel
MEMORY_ONLY(默认):常规表示方法
MEMORY_ONLY_SER:通过分区中的元素序列化为字节数组来实现,比默认的多了一份CPU开销,但减少内存,还能减少垃圾回收的压力
MEMORY_AND_DISK:不适合存储在内存,就将其溢出到磁盘
MEMORY_AND_DISK_SER:如果序列化数据集的大小不适合保存到内存中,就溢出到磁盘
序列化:
数据序列化 和 函数序列化(闭包函数)
数据:Kryo(高效的通用Java序列化库)
conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer")
优化:如果使用前,先在Kryo中对这些类进行注册,可以提高性能
class Cust extends KryoRegistrator{...}
conf.set("spark.kryo.registrator","Cust")
函数:若不可序列号,应该在开发初期就发现它
共享变量:分布式只读共享变量
广播变量(broadcast variable):
在经过序列化后被发送给各个executor,然后缓存在那里,以便后期任务可以在需要时访问,为单向传播,即从driver到任务,因此无法被更新。
sc.broadcast()
累加器: 分布式只写共享变量
在任务中只能对它做假发的共享变量。当作业完成后,driver程序可以检索累加器的最终值。
sc.accumulator(0)
剖析Spark作业运行机制:
最高层:driver(任务调度,一般不由集群管理器管理的客户端运行),executor(专属应用,集群的计算机上)
执行action操作,内部看:
1 SparkContext 调用了 runJob()
调度程序:
2 DAG调度程序(DAGScheduler):
把作业分成若干阶段(stages),这些阶段构成了一个完整的DAG,然后把每个阶段的任务集合提交给任务调度
作业类型:
shuffle map任务 和result 任务
3 任务调度程序(TaskScheduler):把每个阶段中的任务提交到集群
在斟酌位置偏好的同时构建任务到executor的映射。
接着讲任务分配具有可用内核的executor
executor完成后分配更多任务,直至全部完成(默认每个任务一个内核,可修改)
分配顺序:进程本地任务,节点本地任务,机架本地任务,最后任意分配
4 调度程序后端启动,向Executor(SchedulerBackend)后端发送远程启动任务消息
5 Executor后端接收
6 执行Executor
7 ShuffleMapTask or ResultTask
Executor:
a 任务确保任务的JAR包和文件依赖关系是最新的
b 反序列化发过来的一部分序列号代码
c 执行代码,因为任务运行在与executor相同的JVM中,因此任务的启动没有进程开销
8 执行后,以状态更新消息的形式,将driver返回序列号后的执行结果。
shuffle map 任务返回的是一些可以让下一个阶段检索其输出分区的消息
result 任务返回其运行的分区的结果值。
9 driver 将结果收集起来,并把最终结果返回给用户的程序
shuffle map任务:
像MapReduce中的shuffle的map端部门。每个任务在一个RDD分区上运行计算,并根据分区函数把输出写入一组新的分区中,以允许在后面的阶段中取用。shuffle map任务运行在除最终阶段之外的其他所有阶段中。
result 任务:
运行在最终阶段,并将结果返回给用户程序。每个result任务在它自己的RDD分区上运行计算,然后把最终结果发送回driver,再由driver将每个分区的计算结果汇集成最终结果
最简单的Spark作业只有一个由result任务构成阶段
复杂的作业设计到分组操作,回要求一个或多个shuffle阶段
Spark的shuffle实现将其输出写入本地磁盘的分区文件中(即使对内存中的RDD也一样),并且这些文件将由下一个阶段的RDD读取
集群管理器:
本地模式,独立模式,Mesos模式,YARN模式
宽依赖:
父RDD每个分区的数据可能被多个子RDD分区使用,子RDD分区通常对应所有的父RDD分区
1 一个父RDD分区 对应 所有的子RDD分区 (没有co-patitioned过的join)
2 一个父RDD分区 对应 非全部的多个RDD分区 (groupByKey)
窄依赖:
父RDD每个分区的只被一个子RDD分区使用,子RDD通常对应多个父RDD分区
1 一个子RDD分区 对应 一个父RDD分区 (filter,map)
2 一个子RDD分区 对应 多个父RDD分区 (co-patitioned过的join)
优缺点:
宽依赖:有shuffle,要跨网络去拉取资源,耗资源;当子RDD需要重算时,会重算所有父RDD分区数据
窄依赖:一个节点内完成转化,快速;当子RDD需要重算时,只需计算对应的一个父RDD数据即可
宽依赖:reduceByKey,groupByKey,combineByKey,sortByKey,join(no copartition)
窄依赖:filter,mao,flatmao,mapPartitions
Spark的作业(job)比MapReduce的作业更通用,因为Spark作业是由任意的多阶段(stages)有向无环图(DAG)构成,其中每个阶段大致相当于MapReduce中的map阶段或者reduce阶段。这些阶段又被Spark分成多个任务(task),并行运行在分布于集群中的RDD分区上,
Spark作业始终运行在应用(application)上下文(用SparkContext实例来表示)中,它提供了RDD分组以及共享变量
任务划分:
RDD任务切分中间分为:Applicatition,Job,Stask,Task
Applicatition:初始化一个SparkContext,即生成一个Applicatition
Job:一个Actition算子就会生成一个Job
Stage:根据RDD之间的依赖关系的不同将Job划分为不同的Stage,遇到一个宽依赖则划分为一个Stage
Task:Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行,即为一个Task