spark是什么
Spark 计算模式也是属于MapReduce 模式。Spark框架是对MR框架的优化。
两者的对比
Diff | MR | Spark |
---|---|---|
数据存储 | HDFS的split | RDD对数据计算 |
编程范式 | Map Reduce 表达能力不强 | 算子操作 |
计算过程 | 落盘数据,IO代价大 | 中间结果在内存 |
运行方式 | 进程运行Task | Task以线程方式执行 |
大数据应用主要有
分类 | 框架 |
---|---|
离线处理 | MapReduce |
交互式查询 | hive |
流处理 | storm |
spark 实现了 all in one。还提供了机器学习和图计算的库。
所以为什么Spark比MR快:
- 积极使用内存
- 线程方式执行任务,减少进程启动的消耗。
spark怎么用
运行模式有本地/伪分布式/集群
本地模式不用启动master/worker
集群以standalone/yarn/mecos 三种方式部署。
standalone 集群提交任务
- client 模式
spark-submit --class org.apache.spark.examples.SparkPi \
$SPARK_HOME/examples/jars/spark-examples_2.11-2.4.5.jar 1000
- Cluster 模式
spark-submit --class org.apache.spark.examples.SparkPi \
--deploy-mode cluster \
$SPARK_HOME/examples/jars/spark-examples_2.11-2.4.5.jar 1000
编程范式和怎么写代码
spark 定义了一个用于并行计算的集合RDD。它是
- 一组数据分区
- 对分区进行计算compute
- 维护RDD之间的依赖关系
- 对于kv 的RDD,包含一个partitioner
- 保存每个分区的(preferred location)
依赖:
注意:join 并不一定是宽依赖。
RDD可以缓存,下次使用就直接用,以加速计算。
RDD可以持久化数据(checkpoint),切断之前的血缘关系,如果后续出错,再用血缘关系来创建会非常慢,直接从checkpoint这里读数据可以提升性能。
编程范式:
- read data
- transform
- action
transform 是惰性求值,action触发 真正的计算。
常见RDD算子
transform
map/mapPartition
map:每次处理一条数据
mapPartitions:每次处理一个分区的数据,分区的数据处理完成后,数据才能释放,资源不足时容易导致OOM
最佳实践:当内存资源充足时,建议使用mapPartitions,以提高处理效率
宽依赖的算子(shuffle):groupBy、distinct、repartition、sortBy、intersection、subtract
action
Action触发Job。一个Spark程序(Driver程序)包含了多少 Action 算子,那么就有多少Job;
collect() / collectAsMap()
stats / count / mean / stdev / max / min
reduce(func) / fold(func) / aggregate(func)
first / take /top/ foreach
aggregate 若有初始值,则结果是每个分区使用了初始值再加上全局汇总时使用初始值的总和。
常见PairRDD算子
transform
-
mapValues / flatMapValues / keys / values,这些操作都可以使用 map 操作实现,是简化操作。对k-v中的每个k都对应v中的mapValues操作。
-
PariRDD(k, v)使用范围广,聚合
groupByKey / reduceByKey / foldByKey / aggregateByKey
combineByKey(OLD) / combineByKeyWithClassTag (NEW) => 底层实现
subtractByKey:类似于subtract,删掉 RDD 中键与 other RDD 中的键相同的元素
Demo
val rdd = sc.makeRDD(Array(("spark", 12), ("hadoop", 26), ("hadoop", 23), ("spark", 15),
("scala", 26), ("spark", 25), ("spark", 23), ("hadoop", 16), ("scala", 24), ("spark", 16)))
// groupByKey
rdd.groupByKey().map(x=>(x._1, x._2.sum.toDouble/x._2.size)).collect
rdd.groupByKey().map{case (k, v) => (k, v.sum.toDouble/v.size)}.collect
rdd.groupByKey.mapValues(v => v.sum.toDouble/v.size).collect
// reduceByKey
rdd.mapValues((_, 1))
.reduceByKey((x, y)=> (x._1+y._1, x._2+y._2))
.mapValues(x => (x._1.toDouble / x._2)). collect()
// foldByKey
rdd.mapValues((_, 1)).foldByKey((0, 0))((x, y) => {
(x._1+y._1, x._2+y._2) }).mapValues(x=>x._1.toDouble/x._2).collect
// aggregateByKey
// aggregateByKey => 定义初值 + 分区内的聚合函数 + 分区间的聚合函数
rdd.mapValues((_, 1))
.aggregateByKey((0,0))( (x, y) => (x._1 + y._1, x._2 + y._2), (a, b) => (a._1 + b._1, a._2 + b._2))
.mapValues(x=>x._1.toDouble / x._2). collect
// 初值(元祖)与RDD元素类型(Int)可以不一致
rdd.aggregateByKey((0, 0))( (x, y) => {println(s"x=$x, y=$y"); (x._1 + y, x._2 + 1)}, (a, b) => {println(s"a=$a, b=$b"); (a._1 + b._1, a._2 + b._2)} )
.mapValues(x=>x._1.toDouble/x._2).collect
// 分区内的合并与分区间的合并,可以采用不同的方式;这种方式是低效的!
rdd.aggregateByKey(scala.collection.mutable.ArrayBuffer[Int]())( (x, y) => {x.append(y); x}, (a, b) => {a++b} ).mapValues(v => v.sum.toDouble/v.size).collect
// combineByKey(理解就行)
rdd.combineByKey( (x: Int) => {println(s"x=$x"); (x,1)}, (x: (Int, Int), y: Int) => {println(s"x=$x, y=$y");(x._1+y, x._2+1)}, (a: (Int, Int), b: (Int, Int)) => {println(s"a=$a, b=$b");(a._1+b._1, a._2+b._2)} ).mapValues(x=>x._1.toDouble/x._2).collect
// subtractByKey val
rdd1 = sc.makeRDD(Array(("spark", 12), ("hadoop", 26), ("hadoop", 23), ("spark", 15)))
val rdd2 = sc.makeRDD(Array(("spark", 100), ("hadoop", 300))) rdd1.subtractByKey(rdd2).collect()
// subtractByKey
val rdd = sc.makeRDD(Array(("a",1), ("b",2), ("c",3), ("a",5), ("d",5)))
val other = sc.makeRDD(Array(("a",10), ("b",20), ("c",30))) rdd.subtractByKey(other).collect()
注意的是,groupByKey 没有map端combiner,shuffle 过程传输数据量大,效率低。ReduceByKey 在map端有combiner, shuffle过程效率高。
-
排序
sortByKey:sortByKey函数作用于PairRDD,对Key进行排序 -
join
cogroup / join / leftOuterJoin / rightOuterJoin / fullOuterJoin
action
collectAsMap / countByKey / lookup(key)
代码上传执行
// 6、打包,使用spark-submit提交集群运行 //
spark-submit --master local[*] --class cn.lagou.sparkcore.WordCount \
original-LagouBigData-1.0-SNAPSHOT.jar /wcinput/*
spark-submit --master yarn --class cn.lagou.sparkcore.WordCount \
original-LagouBigData-1.0-SNAPSHOT.jar /wcinput/*
RDD 进阶
序列化
对于自定义的RDD操作,在Driver中初始化,在Executor中执行。涉及到进程间通信,就需要Ser/Deser 了。
------------------ demo -----------------------
RDD依赖关系
持久化和缓存
cache() == persist(StorageLevel.Memeory_ONLY)
容错机制checkpoint
通过将RDD写入高可靠的磁盘,主要目的是为了容错。实现方式时执行完任务后,新启线程从后往前找到checkpoint点后执行。所以高效的方式时checkpoint之前cache一下。
以下场景适合使用检查点机制:
- DAG中的Lineage过长,如果重算,则开销太大
- 在宽依赖上做 Checkpoint 获得的收益更大
RDD分区
只讨论分布式模式
spark.default.parallelism = max(应用程序持有executor的core总数, 2)
SparkContext 初始化时
// 从集合中创建RDD的分区数
sc.defaultParallelism = spark.default.parallelism
// 从文件中创建RDD的分区数
sc.defaultMinPartitions = min(spark.default.parallelism, 2)
广播变量
使用广播变量的过程如下:
- 对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T] 对象。 任何可序列化的类型都可以这么实现(在 Driver 端)
- 通过 value 属性访问该对象的值(在 Executor 中)
- 变量只会被发到各个 Executor 一次,作为只读值处理
累加器
累加器的作用:可以实现一个变量在不同的 Executor 端能保持状态的累加;
累计器在 Driver 端定义,读取;在 Executor 中完成累加;
累加器也是 lazy 的,需要 Action 触发;Action触发一次,执行一次,触发多次,执行多次;
累加器一个比较经典的应用场景是用来在 Spark Streaming 应用中记录某些事件的数量;
优化TopN
Spark 原理
四大部分
Driver:用户编写的 Spark 应用程序就运行在 Driver 上,由Driver 进程执行
Master:主要负责资源的调度和分配,并进行集群的监控等职责
Worker:Worker 运行在集群中的一台服务器上。负责管理该节点上的资源,负责启动启动节点上的 Executor
Executor:一个 Worker 上可以运行多个 Executor,Executor通过启动多个线程(task)对 RDD 的分区进行并行计算
SparkContext 三大组件
- DAGScheduler:负责将DAG划分成若干个Stage
- TaskScheduler:将DAGScheduler提交的 Stage(Taskset)进行优先级排序,再将 task 发送到 Executor
- SchedulerBackend:定义了许多与Executor事件相关的处理,包括:新的executor注册进来的时候记录executor的信息,增加全局的资源量(核数);executor更新状态,若任务完成的话,回收core;其他停止executor、removeexecutor等事件
shuffle 原理
RDD 编程优化
- RDD 复用
- RDD cache/persist
- filter
- 使用高性能算子
- 避免使用groupByKey,根据场景选择使用高性能的聚合算子 reduceByKey、aggregateByKey
- coalesce、repartition,在可能的情况下优先选择没有shuffle的操作
- foreachPartition 优化输出操作
- map、mapPartitions,选择合理的选择算子
- 用 repartitionAndSortWithinPartitions 替代 repartition + sort 操作
- 合理使用 cache、persist、checkpoint,选择合理的数据存储级别
- 减少对数据源的扫描(算法复杂了)
- 合理的并行度
- Spark作业中的并行度指各个stage的task的数量
- 设置合理的并行度,让并行度与资源相匹配。简单来说就是在资源允许的前提下,并行度要设置的尽可能大,达到可以充分利用集群资源。合理的设置并行度,可以提升整个Spark作业的性能和运行速度
- 使用广播变量