Spark介绍
- 一个分布式的并行计算框架
- spark是下一代的map-reduce,扩展了mr的数据处理流程
- executor都是装载在container里运行,container默认内存是1G(参数yarn.scheduler.minimum-allocation-mb定义)
- AM(Application Master)在Spark中叫driver,AM向RM申请的是executor资源,当分配完资源后,executor启动后,由spark的AM向
- executor分配task,分配多少task、分配到哪个executor由AM决定,可理解为spark也有个调度过程,这些task都运行在executor的坑里
- Executor有线程池多线程管理这些坑内的task
Executor伴随整个app的生命周期(申请的进程的内存一直到任务结束)
线程池模型,省去进程频繁启停的开销
MapReduce的问题
调度慢,启动map、reduce太耗时
计算慢,每一步都要保存中间结果落磁盘
API抽象简单,只有map和reduce两个原语(需要通过脚本把map和reduce串起来)
缺乏作业流描述,一项任务需要多轮mr
具体描述:
MR中 : 启动map进程,启动reduce进程,所以启动慢
spark:先启动executor进程,再启动线程:比如10个线程:8 map task(线程)2 reduce(线程)
MR:map->reduce的中间结果在磁盘
spark:map->reduce的中间结果也在磁盘(默认)
除非,进行cache(它调用的是persist)默认在内存中
spark只需要通过代码就能把很多的数据处理串在一起:
df = spark.map.reduce
df1 = df.groupBy().count()
通过spark shell的方式查看具体的数据
Spark解决的问题
– 最大化利用内存cache(内存计算下,Spark比MR快100倍)
– 中间结果放内存,加速迭代
input --> iter1 --> 分布式内存 --> iter2 分布式内存
– 某结果集放内存,加速后续查询和处理,解决运行慢的问题
input --> 分布式内存 --> query1,query2,...
- 丰富的API(解决API单一问题)
包括Transformations和Action
Transfomation变换的api,比如map可对每一行做变换,filter过滤出符合条件的行等,这些API实现用户算法,灵活。
spark提供很多转换和动作,很多基本操作如Join,GroupBy已经在RDD转换和动作中实现。不需用户自己实现。写操作就是action:写磁盘,写内存
cache属于transformation,df.cache告诉任务df需要执行之后放到内存中
- 完整作业描述,将用户的整个作业穿起来。关键是这3行。可以立即解释。不像mr那样,需要实现多个map和reduce脚本,解决MR缺乏作业流描述问题
val file = sc.textFile(hdfs://input)
val counts = file.flatMap(
line => line.split(" "))
.map(word => (word, 1))
.reduceByKey(_ + _)
counts.saveAsTextFile(hdfs://output)
Spark核心
• Spark基于弹性分布式数据集(RDD)模型。
• RDD( Resilient Distributed Dataset ):弹性分布式数据集(相当于集合),它的本质是数据
集的描述(只读的、可分区的分布式数据集),而不是数据集本身
• RDD的关键特征:
RDD使用户能够显式将计算结果保存在内存中,控制数据的划分
使用更丰富的操作来处理,只读(由一个RDD变换得到另一个RDD,但是不能对本身的RDD修改)
记录数据的变换而不是数据本身保证容错(lineage)
• RDD采用数据应用变换(map,filter,join),若部分数据丢失,RDD拥有足够的信息得知这部分数据是如何计算得到的,
可通过重新计算来得到丢失的数据
容错:1.备份数据 2.记录更新方式
变换方式:rdd1->rdd2->rdd3
可以知道:rdd2 = rdd1->rdd2
• 恢复数据方法很快,无需大量数据复制操作,可以认为Spark是基于RDD模型的系统
– 懒操作,延迟计算,action的时候才操作
– 瞬时性,用时才产生,用完就释放
RDD来源:
– 读取数据,如从HDFS中读数据构建RDD(Sparkcontext (sc)是spark的入口,编写spark程序用到
的第一个类,包含sparkconf sparkenv等类)
– 通过别的RDD转换过来
val b = a.map(x => (x, 1))
– 定义一个scala数组
val c = sc.parallelize(1 to 10, 1)
– 有一个已经存在的RDD通过持久化操作生成
val d = a.persist(), a.saveAsHadoopFile(“/xxx/xxx/”)
- Dataframe转换过来,这样就可以从hive过来数据
Spark针对RDD提供的操作:transformations和action
- transformations(如上文)是RDD之间的变换,action会对数据执行一定的操作
- transformations采用懒策略,仅在对相关RDD进行action提交时才触发计算
RDD分区和依赖:
• 每个RDD包含了数据分块/分区(partition)的集合,每个partition是不可分割的
– 实际数据块的描述(实际数据到底存在哪,或者不存在)
– 其值依赖于哪些partition
宽依赖和窄依赖:(rddA=>rddB)
宽依赖: B的每个partition依赖于A的所有partition(需要用到之前所有的数据)
如groupByKey、reduceByKey、join……,由A产生B时会先对A做shuffle分桶
窄依赖: B的每个partition依赖于A的常数个partition(需要用到之前部分的数据)
如map、filter、union等 (图片来自网络)
• 从后往前,将宽依赖的边删掉,连通分量及其在原图中所有依赖的RDD,构成一个stage
• DAG是在计算过程中不断扩展 ,在action后才会 启动计算
• 每个stage内部尽可能多地包含一组具有窄依赖关系的转换,并将它们流水线并行化(pipeline)
• 从后往前,将宽依赖的边删掉,连通分量及其在原图中所有依赖的RDD,构成一个stage
• DAG是在计算过程中不断扩展 ,在action后才会 启动计算
• 每个stage内部尽可能多地包含一组具有窄依赖关系的转换,并将它们流水线并行化(pipeline)
• 每个partition的计算就是一个task,task是调度的基本单位
• 一个stage会等待它包含的其他stage中的任务全部完成才加入调度
• 遵循数据局部性原则,使得数据传输代价最小
1. 如果一个任务需要的数据在某个节点的内存中,这个任务就会被分配至那个节点
2. 需要的数据在某个节点的文件系统中,就分配至那个节点
(此时的调度指的是:由spark的AM来决定计算partition的task,分配到哪个executor上)
• 如果此task失败,AM会重新分配task
• 如果task依赖的上层partition数据已经失效了,会先将其依赖的partition计算任务再重算一遍
• 宽依赖中被依赖partition,可以将数据保存HDFS,以便快速重构(checkpoint)
注: 窄依赖只依赖上层一个partition,恢复代价较少;宽依赖依赖上层所有partition,如果数据丢
失,上层所有partiton都要重算
• 可以指定保存一个RDD的数据至节点的cache中,如果内存不够,会LRU释放一部
分,仍有重构的可能
Spark优化相关
内存管理
• Executor的内存分为3块
第一块:让task执行代码,默认占executor总内存的20%
第二块:task通过shuffle过程拉取上一个stage的task的输出后,进行聚合等操作时使用,默认也占20%
第三块:让RDD持久化时使用,默认占60%
• Task的执行速度和每个executor进程的CPU Core数量有直接关系,一个CPU Core同一
时间只能执行一个线程,每个executor进程上分配到的多个task,都是以task一条线程的
方式,多线程并发运行的。如果CPU Core数量比较充足,而且分配到的task数量比较合
理,那么可以比较快速和高效地执行完这些task线程
重要的参数
• num-executors:该作业总共需要多少executor进程执行(建议50~100个左右合适)
• executor-memory:单个excutor的能使用的内存的大小,executor-memory *num-executors
就是本次任务需要的内存(尽量不要超过最大总内存的1/3~1/2 4G~8G较合适)
• executor-cores:单个executor 执行给的core的数量,不能超过单节点的core的总和;
单个core同一时间只能执行一个task,在不影响其他人作业,且不超过节点的core的上限的时候,这个值越大执行的效率越高;该参数决定每个
executor进程并行执行task线程的能力(不要超过总CPU Core的1/3~1/2 2~4个较合适)
开发原则
1、避免创建重复的RDD
2、尽可能复用同一个RDD
3、对多次使用的RDD进行持久化处理
借助cache()和persist()方法
4、避免使用shuffle类算子
在spark作业运行过程中,最消耗性能的地方就是shuffle过程
将分布在集群中多个节点上的同一个key,拉取到同一个节点上,进行聚合和join处理,比如
groupByKey、reduceByKey、join等算子,都会触发shuffle,map为非shuffle算子
5、使用map-side预聚合的shuffle操作
6、使用Kryo优化序列化性能(不使用默认的Java序列化机制)
Spark技术栈
• Spark和Hadoop关系: Spark依赖于HDFS文件系统,如果是Spark on YARN部署模式,又依赖于YARN计算框架
• Spark Core:基于RDD提供操作接口,利用DAG进行统一的任务规划
• Spark SQL:Hive的表 + Spark的里。通过把Hive的HQL转化为Spark DAG计算来实现
• Spark Streaming:Spark的流式计算框架
• MLIB:Spark的机器学习库,包含常用的机器学习算法
• GraphX:Spark图并行操作库