Spark
- 写在前面
- 知识点整理
- 什么是Spark?
- spark和hadoop作业的区别
- Spark相对于MR解决了什么问题
- RDD(弹行分布式数据集):
- spark参数调优
- spark开发调优
- spark常用组件
写在前面
最近抽时间在看hadoop权威指南以及spark快速大数据分析两本书,整理了一些知识点。需要的请点赞收藏。
知识点整理
什么是Spark?
spark是一个用来实现快速而且通用的集群计算平台。
在速度方面,spark拓展了广泛使用的MR计算框架,而且更高效的支持更多计算模型,例如交互式查询和流式处理。由于spark是基于内存计算,所以速度比MR会快很多,大约有100倍
spark和hadoop作业的区别
- Hadoop
- hadoop中一个mr程序就是一个job,而一个job里面可以有一个或者多个Task,Task又可以去分位Map Task和Reduce Task!
- MapReduce中的每个Task分别在自己的进程中运行完时,进程也就结束
- Spark
- Application:spark-submit提交的程序(由一个driver program和多个job组成)
- Driver:完成任务的调度以及和executor和cluster Manager进行协调
- Driver Program时Spark的核心组件
- 负责构建SparkContent
- 将用户提交的job变成DAG图(类似数据处理的流程图)
- 根据策略将DAG图划分成多个stage,再根据分区从而生成一系列的tasks
- 根据tasks要求向RM申请资源
- 提交任务并且检测任务状态
- Executor:每个Spark executor作为一个YARN容器(container)运行。真正执行task的单元,一个work node上可以有多个executor。
- Job(由多个stage组成):和MR的job不一样,MR中主要是Map和Reduce的job,而Spark的job其实更好区别,一个Action算子(行动操作)就算一个job。比如说count()、collect()
- Task:Task是Spark中的最小执行单元。RDD一般是带有partitions的,每个partition在一个executor上的执行可以认为是一个Task
- Stage(对应一个taskset):是spark中独有的。一般而言一个job会切换成一定数量的stage。各个stage之间按照顺序执行
- taskset:对应一组关联的相互之间没有shuffle依赖关系的Task组成
hadoopMR:多进程模型
- 调度慢,启动Map、reduce太耗时
- 计算慢,每一步都要保存中间结果落到磁盘
- API抽象简单,只有Map和reduce两个原语
- 缺乏作业流的描述,一项任务需要进行多轮MR处理
- 但是相对来说稳定,因为是进程
Spark相对于MR解决了什么问题
- 最大化利用内存cache。中间结果放内存,加速迭代。例如某结果集放内存中,加速后续查询和处理,解决运行慢的问题(减少对hdfs的依赖)
# 原始查询
select name,max(score) from stu where age > 18 group by name;
select name,max(score2) from stu where age > 18 group by name;
# Cache stu
select * from stu where age > 18
rdd.registerastable(cachetable)
# 改造sql
select name,max(score) from cache_table group by name;
select name,max(score2) from cache_table group by name;
- 丰富的算子
- 转化操作(Transformations)
- 行动操作(Actions)
- 完整的作业描述
例如下面这个wordcount,将整个作业穿起来。关键是这3行。可以立即解释。不像MR那样,需要实现多个map和reduce脚本,解决MR缺乏作业流描述问题
val file = sc.textFile(hdfs://input)
val counts = file.flatMap(_.split(" ")).map((_,1)).reduceByKey(_ + _).saveAsTextFile(hdfs://output)
RDD(弹行分布式数据集):
- 本质:数据集的描述(只读的、可分区的分布式数据集),而不是数据集本身
- 关键特征
- 计算结果保存内存中,控制数据的划分
- 记录数据而不是数据本身的变换,保证容错
- 懒操作,执行action算子的时候才操作
- 瞬时性:用的时候才产生,用完就释放
- 创建方式
- 共享文件系统获取,如HDFS
val a = sc.textFile("/data/1.txt") - 通过现有的RDD转换得到
val b = a.map((_, 1)) - 定义一个scala数组
val c = sc.parallelize(1 to 10, 1) - 由一个已经存在的RDD通过持久化操作生成
val d = a.persist()
a.saveAsHadoopFile("/output")
- 操作类型(算子)
- Transformations
map() : 数据处理:来一条处理一条
filter() : 过滤器
flatMap() :一到多数据打平的关系
sample() : 采样
groupByKey() : 指定key做聚合
reduceByKey() :对key的value做计算
union() :去重合并
join():两表聚合
crossProduct():内积
sort():排序
partitionBy():分桶
- Actions
count():聚合总数
collect():明文输出
reduce():精简聚合
lookup(5):Top5
save():保存数据
*groupByKey*
map
union
union
*join*
RDD3
RDD4
RDD1
RDD2
RDD6
RDD5
RDD7
如上图,加*号的都是宽依赖,其它是窄依赖
(rddA => rddB)
- 宽依赖: B的每个partition依赖于A的所有partition
比如groupByKey、reduceByKey、join……,由A产生B时会先对A做shuffle分桶 - 窄依赖: B的每个partition依赖于A的常数个partition
比如map、filter、union……
spark参数调优
- Executor的内存分为三块:
- 程序本身,默认占executor总内存的20%
- shuffle类算子,执行内存默认也是占用20%
- 让RDD持久化时候使用,cache、presist、broadcast,默认占用executor总内存的60%
- Task的执行速度和每个executor进程的CPU Core数量有直接关系,一个CPU Core同一时间只能执行一个线程,每个executor进程上分配到的多个task,都是以task一条线程的方式,多线程并发运行的。如果CPU Core数量比较充足,而且分配到的task数量比较合理,那么可以比较快速和高效地执行完这些task线程
- 参数调优
- num-executors:该作业总共需要多少executor进程执行
-- 建议每个作业设置50-100个左右
- executor-memory:设置每个executor进程的内存, num-executors* executor-memory代表作业申请的总内存量(尽量不要超过最大总内存的1/3~1/2)
-- 设置4G到8G合适
- executor-cores:每个executor进程的CPU Core数量,该参数决定每个executor进程并行执行task线程的能力,num-executors* executor-cores代表作业申请总CPU core数(不要超过总CPU Core的1/3~1/2 )
-- 建议:设置2~4个较合适
- driver-memory:设置driver进程的内存
-- 建议:通常不用设置,一般1G就够了,若出现使用collect算子将RDD数据
全部拉取到Driver上处理,就必须确保该值足够大,否则OOM内存溢出
- spark.default.parallelism:每个stage的默认task数量
-- 建议:设置500~1000较合适,默认一个HDFS的block对应一个task,Spark默认值偏少,这样导致不能充分利用资源
- spark.storage.memoryFraction:设置RDD持久化数据在executor内存中能占的比例,默认0.6,即默认executor 60%的内存可以保存持久化RDD数据
-- – 建议:若有较多的持久化操作,可以设置高些,超出内存的会频繁gc导致运行缓慢
- spark.shuffle.memoryFraction:聚合操作占用内存比例,默认0.2
-- – 建议:若持久化操作较少,但shuffle较多时,可以降低持久化内存占比,提高shuffle操作内存占比
spark开发调优
- 避免创建重复的RDD,重复RDD极大的浪费内存
- 尽可能的复用同一个RDD,可以减少算子的执行次数
- 对多次使用的RDD进行持久化处理
- 每次对一个RDD执行一个算子操作时,都会重新从源头处理计算一遍,计算出那个RDD出来,然后进一步操作,这种方式性能很差
- 对多次使用的RDD进行持久化,将RDD的数据保存在内存或磁盘中,避免重复劳动
- 借助cache()和persist()方法
val a = sc.textFile("/input/data.data").cache
val b = sc.textFile("/input/data.data").persist(StorageLevel.MEMORY_AND_DISK_SER)
// 默认使用MEMORY_AND_DISK_SER的方式(内存充足以内存持久化优先,_SER表示序列化)
- 避免使用shuffle算子,因为spark运行过程,最消耗性能的就是shuffle过程
// 1. shuffle
val rdd3 = rdd1.join(rdd2)
// 2. 不用shuffle
val rdd2_data = rdd2.collect()
val rdd2_data_broadcast = sc.broadcast(rdd2_data)
val rdd3 = rdd1.map(rdd2_data_broadcast .......)
- 使用map-side预聚合的shuffle操作
- 一定要使用shuffle的,无法用map类算子替代的,那么尽量使用map-site预聚合的算子
- 类似于MR中的combiner
- 可能的情况下使用reduceByKey或aggregateByKey算子替代groupByKey算子,因为reduceByKey或aggregateByKey算子会使用用户自定义的函数对每个节点本地相同的key进行预聚合,而groupByKey算子不会预聚合
spark常用组件
- Spark Core : 基于RDD提供的操作接口,利用DAG进行统一的任务规划
- Spark SQL:Hive的表 + Spark。通过把Hive的HQL转化为Spark DAG计算来实现
- Spark Streaming:Spark的流式计算框架
- MLIB:Spark的机器学习库,包含常用的机器学习算法
- GraphX:Spark图并行操作库