一、Spark三种模式
- 本地模式
./bin/run-example SparkPi 10 --master local[2]
- 集群模式 Spark Standalone(Spark独立集群模式)
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://master:7077 \
examples/jars/spark-examples_2.11-2.0.2.jar 100
- 集群模式Spark on yarn(yarn-cluster和yarn-client)
spark运行在yarn集群上,可以与其他计算框架共享资源
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn-cluster \
examples/jars/spark-examples_2.11-2.0.2.jar 100
yarn-cluster和yarn-client本质区别:AM进程
二、集群模式Spark on yarn
2.1 yarn-cluster模式
driver在AM中,AM(driver)负责向yarn申请资源、作业的调度与监控。一旦用户提交完作业后,client可以关闭,作业在yarn集群上运行,数据结果会生成在AM的日志中,适用于生产环境。
- spark-submit脚本提交,向yarn(RM)中提交ApplicationMaster程序、AM启动的命令和需要在Executor中运行的程序等
- RM收到请求之后,选择一个NM,在其上开启一个container,在container中开启AM,并在AM中完成SC的初始化
- AM向RM注册并请求资源,这样用户可以在RM中查看任务的运行情况。RM根据请求采用轮询的方式和RPC协议向各个NM申请资源并监控任务的运行状况直到结束
- AM申请到资源之后,与对应的NM进行通信,要求在其上获取到的Container中开启executor,开启之后,向AM中的SC注册并申请task
- AM中的SC分配task给executor,executor运行task并向AM中的SC汇报自己的状态和进度
- 应用程序完成之后(各个task都完成之后),AM向RM申请注销并关闭自己
ApplicationMaster的作用:
- 当前的Application申请资源
- 给nodemanager发送消息 启动Executor。
- 任务调度
2.2 yarn-client模式
driver在客户端节点上,作业提交后,启动driver进程,AM仅向RM请求executor资源并通知指定的NM启动executor,driver会和对应的executor进行通信来调度任务,client不能离开,适用交互与调试 。
- spark-submit脚本提交,driver进程在客户端本地运行
- Client向RM申请启动AM,同时在SparkContext(client上)中创建DAGScheduler和TaskScheduler
- RM收到请求之后,根据资源分布选择其中一个NM,分配container,并在container中开启AM
- client中的SC初始化完成之后,与AM进行通信,向RM注册,根据任务信息向RM申请资源
- AM申请到资源之后,与NM进行通信,要求在它申请的container中开启executor。Executor在启动之后会向SC注册并申请task
- SC分配task给executor,executor执行任务并向Driver汇报,以便客户端可以随时监控任务的运行状态
- 任务运行完成之后,client的SC向RM注销自己并关闭自己
ApplicationMaster的作用:
- 为当前的Application申请资源
- 给NodeManager发送消息启动Executor
ApplicationMaster有launch Executor和申请资源的功能,并没有作业调度的功能。
Spark standalone
- master/slave结构
- Master:类似yarn中的RM
- Worker:类似yarn中的NM
三、MapReduce和Spark作业的区别
- MapReduce (Hive)的一个map task/reduce task是进程;Spark的一个map task/reduce task是线程
- container(进程需要的资源CPU,内存,IO)是进程。在mapreduce中container作为一个map task或者一个reduce task;在spark container可以同时拥有map task(线程)+ reduce task(线程)
- MapReduce分布式计算框架,解决数据分布式计算问题。spark在MR基础上做分布式多线程,若不使用cache/persist,spark默认会遵循MR执行过程,过程会有shuffle和落地到磁盘。cache()调用persist(memory only),数据缓存到内存,persist()会落地磁盘、内存、序列化等等。
- 一个MR任务就是一个job,一个job分为多个task,task又可以分为map task和reduce task,每个task在自己的进程中运行。在任务执行过程中,运行完的task进程可以释放进程资源。复杂的hadoop任务由多个MR job串行组成;一个spark任务是一个application,一个app可分为多个job(由action算子划分),一个job分为多个stage(由shuffle划分),一个stage中包含多个task(map task 或者 reduce task:线程)。一个job可以有一个进程或者多个进程。进程生命周期和application一样,只有当一个app任务完成时,进程所占用的所有资源才会被释放。一个app任务由多个job并行或者串行运行。
四、Spark的application
- spark中的job和MR中的job意义不一样,spark job是指一个application中的action算子触发一个job,MR job是一个MR任务。
- 一个Application通过SparkContext来解析,每个app可有一个或多个job,并行或串行运行job
- spark中一个action算子触发一个job
- 一个job包含多个stage,stage是以shuffle过程来划分,每个stage包含多个task
- 与MR不同,spark的task(线程)可运行在一个进程中,进程的生命周期和app周期相同,任务完成后资源全部释放
优点:加快spark运行速度(MR任务过程中数据需要落地到磁盘,spark任务中间数据可cache到内存中,加快运算速度),task可快速启动(MR task是进程,启动较慢,spark task是线程,启动快)
缺点:每个application有固定数目的executor和内存(自定义或者默认)
细化内部结构
- application:一个driver和多个job构成
- job:由多个stage组成
- stage:对应一个taskset
- taskset:对应一组关联的相互之间没有shuffle依赖关系的task组成
- task:任务最小的工作单元
Driver Program(spark application入口)
- driver是Spark的核心组件
- 构建SparkContext(spark应用的入口,创建需要的变量,包含集群的配置信息)
- 将一个application任务依据action算子划分多个job,每个job转换为DAG图,根据shuffle将每个DAG图划分为多个stage,根据分区从而生成一系列tasks,再根据tasks向RM申请资源
- 提交任务并检测任务状态
MR存在的问题
- 调度慢:启动map、reduce进程太耗时
- 计算慢:每一步都要保存中间结果到磁盘
- API抽象简单:只有map和reduce
- 缺乏作业流描述:一项复杂任务需要多轮mr
Spark
- spark是下一代的mapreduce,扩展了mr的数据处理流程的分布式并行计算框架
- executor装载在container里运行,container默认内存是1G
- executor分配的内存是executor-memory,向yarn申请的内存是(num-executors+1)* executor-memory
- AM在spark被称为driver,AM向RM申请executor资源,当分配完资源后,executor启动后,由spark的AM向executor分配task,分配多少task,分配到哪个executor由AM决定
- executor由线程池多线程管理这些task
spark解决的问题
- 最大化利用内存cache
- 中间结果放入内存,加速迭代
- 加速后续查询和处理,解决运行慢的问题
- 更加丰富的API(transformation算子和action算子)
- 完整作业流描述:对整个作业串起来
五、Spark核心-RDD
Spark基于弹性分布式数据集RDD模型,具有良好的通用性、容错性与并行处理数据的能力。
RDD(Resilient Distributed Dataset)弹性分布式数据集,本质是数据集的操作描述(只读、可分区),而不是数据集本身
RDD的特性
- RDD将数据集的操作保存在内存中,控制数据的划分,提供丰富的API操作
- RDD本身只读,不可修改(一个RDD转换为另一个RDD)
- 每一个RDD记录了数据的操作符而不是数据本身来保证容错:若部分数据丢失,可根据RDD记录的操作符来恢复
- 每个RDD包含了数据分块/分区的集合,每个partition不可分割。每一个partition的计算是一个task,task是调度的基本单位。
- 懒操作:延时计算,直到action算子才操作
- 瞬时性:用时才产生,用完就释放
遵循数据局部性原则,使数据传输代价最小
- 如果一个任务需要的数据在某个节点的内存中,这个task分配到这个节点
- 需要的数据在某个节点的文件系统中,分配至这个节点(由spark的AM来调度task分配到哪个executor)
RDD提供两类操作:transformations算子和action算子
- transformations是RDD之间的变换,action会对数据执行一定的操作
- transformations采用懒操作,仅当action提交才触发计算
RDD之间的依赖关系
宽依赖和窄依赖(rddA=>rddB)
- 宽依赖:A和B是一对多的关系(shuffle), B的每个partition都依赖于A的每一个partition
例如:groupByKey、reduceByKey、join…… - 窄依赖:A和B是一对一关系 ,B的每个partition都依赖于A的常数个partition
问题:如下的DAG图存在可能性吗?
不存在这种情况;原因是DAG图是通过AM下的的driver依据RDD的数据集以及算子操作来解析的,此时没有输入原始数据真正的执行。等到真正执行数据时,数据的流向可能会如上图所示,但是DAG图应该是全连接的shuffle过程。
六、Spark核心-容错
- 若task失败,AM会重新分配task
- 窄依赖依赖上一层的partition,恢复代价小;宽依赖依赖上层所有的partition,如果下游数据某个partition丢失,上层partition都要重新计算,导致下游partition的重复计算。
- 为了使丢失的数据快速恢复,宽依赖的上层RDD cache到内存。
七、Spark调优
7.1 Spark资源参数调优
executor内存分为3种:
- RDD持久化使用,默认占executor总内存的60%
- task通过shuffle过程拉取上一个stage的task输出,进行聚合等操作时使用,默认占20%
- task执行代码使用,默认占20%
task执行速度和每个executor进程的cpu core数量有直接关系,一个cpu同一时间只能执行一个线程,进程中的tasks是以多线程并发运行的,如果cpu core数量充足,那么可以快速高效的执行完这些task线程。
参数:
- num-executors:作业一共需要多少executor进程执行,建议每个作业运行一般设置5/10/15个左右
- executor-memory:每个executor进程的内存(申请的总内存量不能超过最大总内存的1/3~1/2),建议4G ~ 8G
- executor-cores:每个executor进程的CPU core数量,该参数决定executor进程并行执行task线程的能力,建议2~4个
- driver-memory:driver进程的内存,建议设置多一些(4G),出现collect算子将RDD数据全部拉取到driver上做处理,必须保证该值足够大,否则OOM内存溢出
- spark.default.parallelism:每个stage的默认task数量
- spark.storage.memoryFraction:设置RDD持久化数据到executor内存所占的比例,默认0.6
- spark.shuffle.memeoryFraction:聚合操作占executor内存的比例,默认0.2
spark参数调整可在spark开发中设置conf,或者在提交任务时附加参数配置
7.2 Spark开发调优
- 原则一:避免创建重复的RDD(对于同一份数据,只需要创建一个RDD,不能重复创建,极大浪费内存)
- 原则二:尽可能复用相同的RDD(相同的RDD操作可以共用,不同的RDD操作可以并列执行)
- 原则三:对多次使用的RDD进行持久化处理(cache()/persist())
- 原则四:避免使用shuffle类算子
- spark作业运行过程中,最消耗性能的地方就是shuffle过程
- 将分布在集群中多个节点上的同一个key,拉取到同一节点上,进行聚合或join操作,如groupByKey、reduceByKey、join等,都会触发shuffle
- Broadcast+map的join操作,不会导致shuffle操作,但前提是RDD数据量较少的情况下(类似于hive中mapjoin)
- 原则五:使用map-side预聚合的shuffle操作
- 有shuffle过程且无法用map类算子代替的,尽量使用map-side预聚合算子,类似于MR的combiner
- 使用reduceByKey或aggregateByKey算子代替groupByKey,因为reduceByKey或aggregateByKey算子会使用用户自定义的函数对每个节点本地相同的key进行预聚合,而groupByKey算子不会预聚合
- 原则六:使用Kryo优化序列化性能
- Kryo是一个序列化类库,优化序列化和反序列化性能
- spark默认使用Java序列化机制进行序列化和反序列化
- spark支持使用Kryo序列化库,性能比Java序列化库高10倍左右