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进行协调
  1. Driver Program时Spark的核心组件
  2. 负责构建SparkContent
  3. 将用户提交的job变成DAG图(类似数据处理的流程图)
  4. 根据策略将DAG图划分成多个stage,再根据分区从而生成一系列的tasks
  5. 根据tasks要求向RM申请资源
  6. 提交任务并且检测任务状态
  • 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(弹行分布式数据集):

  • 本质:数据集的描述(只读的、可分区的分布式数据集),而不是数据集本身
  • 关键特征
  1. 计算结果保存内存中,控制数据的划分
  2. 记录数据而不是数据本身的变换,保证容错
  3. 懒操作,执行action算子的时候才操作
  4. 瞬时性:用的时候才产生,用完就释放
  • 创建方式
  1. 共享文件系统获取,如HDFS
    val a = sc.textFile("/data/1.txt")
  2. 通过现有的RDD转换得到
    val b = a.map((_, 1))
  3. 定义一个scala数组
    val c = sc.parallelize(1 to 10, 1)
  4. 由一个已经存在的RDD通过持久化操作生成
    val d = a.persist()
    a.saveAsHadoopFile("/output")
  • 操作类型(算子)
  1. Transformations

map() : 数据处理:来一条处理一条
filter() : 过滤器
flatMap() :一到多数据打平的关系
sample() : 采样
groupByKey() : 指定key做聚合
reduceByKey() :对key的value做计算
union() :去重合并
join():两表聚合
crossProduct():内积
sort():排序
partitionBy():分桶

  1. 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的内存分为三块:
  1. 程序本身,默认占executor总内存的20%
  2. shuffle类算子,执行内存默认也是占用20%
  3. 让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
  • 可能的情况下使用reduceByKeyaggregateByKey算子替代groupByKey算子,因为reduceByKey或aggregateByKey算子会使用用户自定义的函数对每个节点本地相同的key进行预聚合,而groupByKey算子不会预聚合

spark常用组件

  1. Spark Core : 基于RDD提供的操作接口,利用DAG进行统一的任务规划
  2. Spark SQL:Hive的表 + Spark。通过把Hive的HQL转化为Spark DAG计算来实现
  3. Spark Streaming:Spark的流式计算框架
  4. MLIB:Spark的机器学习库,包含常用的机器学习算法
  5. GraphX:Spark图并行操作库