Spark的运行模式
Spark名词
Master:集群中含有Master进程的节点。Master是整个集群的控制器,负责整个集群的正常运行。
Slave:集群中含有Worker进程的节点。Worker是工作节点,接收主节点的命令并进行状态汇报。
Yarn模式下:
ResourceManager:相当于Master,负责整个集群资源的调度。
NodeManager:相当于Worker,负责工作。
ApplicationMaster:相当于Driver。
Container:在NM上启动,负责执行任务。
SparkApplication:包含SparkContext和Executor。
Driver:在client提交Spark程序的时候产生的进程,运行Application中的main函数,初始化SparkContext。
SparkContext:整个Spark应用的上下文,控制应用的生命周期。
Executor:执行器,在NodeManager上执行任务的组件,启动线程池运行任务,每个Application都有一组的Executors。
RDD:Spark中的数据集。
Job:当程序碰到action算子的时候会提交一个job。job之前的操作都是延迟执行的。
Stage:每个job都会被切割成一个或者多个stage。
Task:RDD中的每一个分区都是一个task,task是RDD中最小的处理单元了。
TaskSet:一组task组成taskSet。每组TaskSet会被分发到各个节点上执行。
DAGScheduler:根据每个作业job,切分成一个个的stage。负责切分job
TaskScheduler:将任务taskset分发到每个节点上执行。
RDD
val lines = sc.textFile(args(0), 5)
val maleHeight = lines.filter(line => line.contains("M")).map(line => line.split("\t")(1) + " " + line.split("\t")(2))
val femaleHeight = lines.filter(line => line.contains("F")).map(line => line.split("\t")(1) + " " + line.split("\t")(2))
femaleHeight中需要lines的过滤,这个时候需要重新去读文件。所以最好在lines做一个持久化的动作。
lines.persist() 和 lines.cache()
测试:
读取一个1000000行的数据,未持久化:需要22秒 持久化:需要13秒
读取一个10000000行的数据,未持久化:需要98秒 持久化:需要72秒
尽量避免DiskIO,优化策略。
宽窄依赖
Spark运行过程
集群中提交Spark应用程序
一些算子操作
subtract:返回在rdd1中出现,不在rdd2中出现的元素 相当于rdd1-rdd2
mapPartitions:适用于一个partition中数据不多的时候。一次以迭代器的形式返回一个partition中的所有数据
mapValues:对每个以key, value形式的map中的value操作。rdd.mapValues(x => x + "_")
partitionBy:根据partitioner函数生成新的ShuffleRDD,将原来的RDD重新分区。通过新的分区策略将原来在不同分区的数据都合并到一个分区。
rdd1.partitionBy(new org.apache.spark.HashPartitioner(2))
flatMapValues:同基本转换操作中的flatMap,flatMapValues是针对[K,V] 中的V进行flatMap操作。
sample:抽样 三个参数
sc.parallelize(datas)
.sample(withReplacement = false, 0.5, System.currentTimeMillis)
.foreach(println)
groupByKey():对<key,value>结构的RDD进行类似RMDB的group by聚合操作,具有相同的key的RDD成员的value会被聚合在一起,返回的RDD的结构是(key,Iterator<value>)
reduceByKey:对<key,value>结构的RDD进行聚合,对具有相同Key的value调用func来进行reduce操作,func的类型必须是<value,value> => v 对相同key的value进行操作。rdd2.reduceByKey((a,b) => (a + b)/4).collect().foreach(println)
sortByKey:对<key, value>结构的RDD进行升序或者降序排列。两个参数
comp:排序时的比较运算方式。
ascending:false降序;true升序。
join操作:对<K, V>和<K, W>进行join操作,返回(K, (V, W))外连接函数为leftOuterJoin、rightOuterJoin和fullOuterJoin
cogroup:对多个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。与reduceByKey不同的是针对两个RDD中相同的key的元素进行合并。
cartesian:两个RDD进行笛卡尔积合并
action操作:
reduce:对RDD成员使用func进行reduce操作,func接收两个参数,合并之后返回一个值,reduce操作的返回结果只有一个值,需要注意的是,reduce会并发执行。
collect:将RDD读取到Driver所在的内存,类型是Array,一般要求RDD不要太大
count:返回RDD的成员数量
first:返回RDD的第一个成员。等价于take(1)
take(n):返回RDD的前n个成员。sc.parallelize( 1 to 10).take(2).foreach(println)
takeSample:和sample用法相同,第二个参数换成了个数,返回也不是RDD,而是collect
saveAsTextFile:将RDD转化为文本内容并保存到路径path下,可能有多个文件(和Partition数量有关),路径path可以是本地路径,也可以是HDFS地址,转换方法时对RDD成员调用toString函数
saveAsSequenceFile:与saveAsTextFile类似,但以SequenceFile格式保存,成员类型必须实现Writeable接口或可以被隐式转换为Writable类型(比如基本Scala类型Int、String等)
saveAsObjectFile:用于将RDD中的元素序列化成对象,存储到文件中。对于HDFS,默认采用SequenceFile保存。
countByKey:适用于<K,V>类型,对key计数,返回(K,Int)
println(sc.parallelize(Array(("A", 1), ("B", 6), ("A", 2), ("C", 1), ("A", 7), ("A", 8))) .countByKey())
Map(B -> 1, A -> 4, C -> 1)
glom:glom函数将每个分区形成一个数组,
union:返回的RDD的数据类型和被合并的RDD的元素数据类型一样,通过符号++相当于union操作
combineByKey:相当于将元素(Int,Int)的RDD转变为了(Int,Seq[Int])类型的RDD 。将 (V1,2), (V1,1)数据合并为( V1,Seq(2,1))。
reduceByKey:reduceByKey是比combineByKey更简单的一种情况,知识两个值合并成一个值将 (V1,2), (V1,1)数据合并为( V1,3)。
reduce:f:(A,B) => (A._1 + "@" + B._1,A._2 + B._2)A和B可以代表<key, value>形式的数据,而不仅仅是单个数据
fold:折叠
reduceByKye = groupByKey + reduce 。map端和reduce端执行的逻辑一样。
shuffle = map端 + reduce端
spark的reduceByKey在map端自带combine。可以用来计算sum。不能计算average。
aggregateByKey:当map端和reduce端执行逻辑不一样的时候,可以用aggregateByKey。
unit:不会发生shuffle。只是逻辑上的抽象。
distince:有shuffle动作。
countByKey:会产生shuffle。其他的ByKey都会产生shuffle,join也会 cogroup也会。
cogroup:join相当于把cogroup中的value值进行排列组合。
Spark读取文件
--class com.ibm.spark.exercise.basic.SparkWordCount \
--master spark://hadoop036166:7077 \
--num-executors 3 \
--driver-memory 6g --executor-memory 2g \
--executor-cores 2 \
/home/fams/sparkexercise.jar \
hdfs://hadoop036166:9000/user/fams/*.txt
Spark的缓存策略
数据大概:一万行一mb。
Spark-env.sh
export SCALA_HOME=scala地址
export HADOOP_HOME=hadoop地址
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export SPARK_MASTER_IP=node
export SPARK_WORKER_CORES=1 cpu资源
export SPARK_WORKER_MEMORY=512m 使用多大内存
export SPARK_WORKER_INSTANCES=1 每个节点有几个worker进程
export SPARK_HOME=spark地址
export PATH=$SPARK_HOME/bin:$PATH
export YARN_CONF_DIR=$HADOOP_HOME/etc/hadoop
Spark调优
Yarn会根据Spark作业设置的资源参数,在每个工作节点上,启动一定数量的Executor进程,每个进程都占有一定数量的CPU和内存。
Spark的资源参数调优:
num-executors:设置整个Spark作业总共需要多少个Executor进程来执行。如果不设置的话,系统只会启动少量的Executor进程。一般设置50-100个Executor进程。
executor-memory:设置每个Executor进程的内存。设置4G-8G比较合适,需要看集群的整体资源。
executor-cores:设置每个Executor进程的CPU core数量。决定了Executor进程并行执行task线程的能力。设置2-4个比较合适。
driver-memory:设置Driver进程的内存。一般1g左右就足够,但是如果代码中有collect操作的话,需要考虑内存溢出的情况。
spark.default.parallelism:设置每个stage默认的task数量。Spark根据底层HDFS的block数量来设置task的数量,默认是一个HDFS block对应一个task,这样的task数量是偏少的。如果task数量偏少的话,那么之前设置的那些资源可能得不到很好的利用。很多Executor进程可能空闲。根据Spark官网的设置原则,设置参数为num-executors*executor-cores的2-3倍。比如Executor的总cpu core的数量是300个那么设置1000个task是可以的。(其实就是看Executor总得占有的CPU core的数量的2-3倍)
spark.storage.memoryFraction 该参数用于设置RDD持久化数据在Executor内存中能占的比例,默认是0.6。
spark.shuffle.memoryFraction:该参数用于设置shuffle过程中一个task拉取到上个stage的task的输出后,进行聚合操作时能够使用的Executor内存的比例,默认是0.2。
--master yarn-cluster \
--num-executors 100 \
--executor-memory 6G \
--executor-cores 4 \
--driver-memory 1G \
--conf spark.default.parallelism=1000 \
--conf spark.storage.memoryFraction=0.5 \
--conf spark.shuffle.memoryFraction=0.3 \
--master yarn-cluster \
--num-executors 100 \
--executor-memory 6G \
--executor-cores 4 \
--driver-memory 1G \
--conf spark.default.parallelism=1000 \
--conf spark.storage.memoryFraction=0.5 \
--conf spark.shuffle.memoryFraction=0.3 \
优化:使用mapPartitions代替map
使用foreachPartitions代替foreach:这两个需要考虑内存溢出的问题。一次处理一个partition的数据
使用filter之后进行coalesce操作。
repartitionAndSortWithinPartitons代替repartition和sort:如果需要在repartition重分区之后,还要进行排序,建议直接使用repartitionAndSortWithinPartitions算子。因为该算子可以一边进行重分区的shuffle操作,一边进行排序。shuffle与sort两个操作同时进行,比先shuffle再sort来说,性能可能是要高的。
广播变量:广播后的变量,保证每个Executor的内存中,只有一个变量副本,每个Executor的task公用这个副本,这样可以大大减少副本的数量,减少网络的开销。
可能发生数据倾斜的算子:distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition等
解决数据倾斜:
1.hive预处理。
2.过滤少数的key
3.提高shuffle并行度
4.两阶段聚合(局部聚合 全局聚合)给每个key加上随机数,接着如果进行reduceByKey等聚合操作,不同的key分配到不同的task中,然后再进行全局聚合,得到最后结果。适用于聚合类的shuffle操作,不适合join类的shuffle操作。
5.扩容:采样倾斜key并拆分join操作:对于join导致的数据倾斜,如果只是某几个key导致了倾斜,可以将少数几个key拆分成独立RDD,并附加随机前缀打散成n份进行join。过滤出来那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按顺序附加一个0~n的前缀,不会导致倾斜的大部分key也形成另外一个RDD。
Spark中的共享变量
容错性保证
优化
问题
在执行算子map操作的时候,如果不是一个简单的匿名函数表达式,则应该用{},不是()
如果不进行checkpoint的话可能DAG图太长了,导致栈溢出。
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("PageRank").setMaster("local[2]")
val item = 200
val sc = new SparkContext(conf)
val lines = sc.textFile("D:\\MYFile\\IDEAFiles\\MyScala\\src\\main\\resource\\page_rank.txt")
sc.setCheckpointDir(".")
//根据边关系生成(1,(2,3,4,5)), (2,(1,4)), (3,(2,4))
val links = lines.map{
s => val pairs = s.split("\\s+")
(pairs(0), pairs(1))
}.distinct().groupByKey().cache()
//生成(1,1.0) (2,1.0)..
var ranks = links.mapValues(v => 1.0)
for(i <- 1 to item) {
//join操作:(1,((2,3,4,5),1.0))
val contribs = links.join(ranks).values.flatMap{
case (urls, rank) =>
val size = urls.size
urls.map(urls => (urls, rank/size))
}
ranks= contribs.reduceByKey(_ + _).mapValues(0.15 + 0.85 * _)
//如果不进行checkpoint的话DAG太长了
ranks.checkpoint()
}
ranks.foreach(tup => println(tup._1 + "has rank : " + tup._2 + "."))
}
}
数据集RDD1和RDD2中存在数据量很大且倾斜的key,目的RDD1.join(RDD2):
RDD1中的key加前缀Random(10)生成rdd11,RDD2扩容10倍生成rdd22。rdd11.join(rdd22)
伪代码:
val rdd11 = RDD1.flatMap(key => Random(10)_key)
val rdd22 = RDD2.flatMap
{key =>
for(i <- 0 to 9) {
i_key
}
}
--master yarn-cluster \
--num-executors 100 \
--executor-memory 6G \
--executor-cores 4 \
--driver-memory 1G \
--conf spark.default.parallelism=1000 \
--conf spark.storage.memoryFraction=0.5 \
--conf spark.shuffle.memoryFraction=0.3 \