本文内容:
- spark core:
spark概念/集群/基本操作
spark的RDD 是什么/怎么创建/怎么用/RDD上的算子
spark任务的运行机制
spark的高级特性:缓存,持久化,广播变量,累加器
spark on yarn zookeeper
spark HA - Spark SQL
- kafka+sparkStreaming实时处理
- flume 数据采集工具
Spark
一个基于内存的,分布式的大数据计算引擎。(大数据并行框架)
- 和hadoop(mapreduce)的比较:
hadoop:hdfs(分布式存储) + mapreduce(分布式计算) + yarn(统一资源调度平台)
MapReduce:读-处理-写磁盘-读-处理-写
spark:读----处理----处理----(需要时)写磁盘 -写
spark是在借鉴了MapReduce之上发展而来的,继承了分布式并行计算的优点。并改进了MapReduce的缺陷。 - 首先Spark把中间数据放到内存中,迭代运算效率高。MapReduce中计算结果需落地,保存到磁盘,影响整体速度。而Spark支持DAG图的分布式并行计算的编程框架,减少了迭代过程中数据的落地,提高了处理效率(延迟加载)。
- 其次,Spark容错性更高,Spark引进了弹性分布式数据集,RDD的抽象,它是分布在一组节点中的只读对象集合,这些集合是弹性的,如果数据丢失一部分,则可以根据血统(即允许基于数据衍生过程)对其进行重建。另外在RDD计算是可以通过CheckPoint来实现容错。
- 最后,Spark更加通用,MapReduce只提供了map和reduce两种操作,Spark提供的数据集操作类型有很多,大致分为:Transformations和Actions两大类。Transformations包括Map,Filter,FlatMap,Sample,GroupByKey,ReduceByKey,Union,Join,Cogroup,MapValues,Sort等多种操作类型,同时还提供Count,Actions包括Collect,Reduce,Lookup和Save等操作。
支持的运算平台,开发语言更多。(scala,java,Python,R)
总结:
Spark是MapReduce的替代方案,而且兼容HDFS,Hive,yarn,可融入hadoop生态系统,弥补MapReduce的不足。
优点:
- 速度快,Spark基于内存的计算要快100倍以上,基于硬盘的运算要快10倍以上,spark实现了高效的DAG执行引擎,可以通过基于内存来高效处理数据流。
- 易用,支持scala,java,Python,和R的api,还支持超过80种高级算法,使用户可以快速构建不同的应用。而且Spark支持交互式的Python和Scala的shell,可方便在shell中使用spark集群验证解决问题的方法。
- 通用,一站式解决方案。Spark中支持:离线处理,结构化数据处理,实时处理,图计算,机器学习。
- 兼容性,spark可运行在hadoop,mesos,standalone,hive,HBASE上。可以和hadoop生态圈中很多组件兼容,如hdfs,yarn,hive,HBASE等。
Spark部署模式:
本地模式+3种集群模式,具体使用哪种模式由其(–master)参数决定。
-
local模式:本地模式,一台机器,开箱即用
- -master local (指定或不指定master都是local模式)
- -master local (1个cores)
- -master local [N] (N个cores)
- -master local [*] (所有的cores) -
standalone集群模式:是spark自带的集群模式,多台机器
- -master spark://hadoop000:7077 -
yarn集群模式:把spark任务提交到yarn(统一资源调度平台)上运行。
- -master yarn -
Mesos集群模式:mesos也是一个资源调度平台。
- -master mesos://host:port
standalone集群搭建
集群环境准备:
3台机器;
Spark中的角色:Master,Worker
机器环境准备:
- 免密登录:worker节点到master节点
- 集群防火墙和windows防火墙关闭
- 映射关系
- jdk环境(每台机器)jdk1.8+
安装spark环境不需要单独安装scala,spark包中已经包含scala.jar
解压缩安装文件
tar -zxvf spark-2.4.4-bin-hadoop2.7.tgz
cd spark-2.4.4-bin-hadoop2.7
cd conf/
cp spark-env.sh.template spark-env.sh
vim spark-env.sh
找到该行注释
# Options for the daemons used in the standalone deploy mode
常用配置:
# - SPARK_MASTER_HOST, to bind the master to a different IP address or hostname
# - SPARK_MASTER_PORT / SPARK_MASTER_WEBUI_PORT, to use non-default ports for the master
末尾添加:
export JAVA_HOME=/soft/jdk
export SPARK_MASTER_HOST=hadoop000
export SPARK_MASTER_PORT=7077
生成java_home的方式:
: r! echo $JAVA_HOME
修改slaves.template文件
cp slaves.template slaves
vim slaves
删掉localhost,写入所配置的从节点。
# A Spark Worker will be started on each of the machines listed below.
hadoop001
hadoop002
安装包分发
[root@hadoop000 spark]# for i in 1 2; do scp -r spark-2.4.4-bin-hadoop2.7/ hadoop00$i:$PWD; done
集群的启停操作
sbin/文件夹为所有集群操作命令
单独启动master:
cd spark-2.4.4-bin-hadoop2.7/sbin
./start-master.sh
jps
8080端口为webUI端口
单独启动worker节点:
在worker机器上:
cd /spark/sbin
./start-slave.sh spark://hadoop:7077
一键启动所有worker
停止:
stop-master.sh
stop-slaves.sh
master和worker全部启停:
start-all.sh
stop-all.sh
提交任务到spark命令
提交主要有两个命令
cd bin
spark-shell
spark-submit
#其实两个是同样的命令,验证:
cat spark-shell | grep spark-submit
spark-shell 主要用于交互式的命令行,用于练习测试
spark-submit 真正运行程序的api
spark-submit 执行命令的套路:
sparkSubmit常用选项:
- -master 指定程序运行的部署模式
- -class 指定程序运行的主类
提交任务的地方:客户端
只要能连接上Master地址,且具有spark的安装包,就可以提交spark任务。
第一个spark示例:
#未指定--master,则默认local模式
#local模式下不会出现监控界面
[root@hadoop000 bin]# spark-submit --class org.apache.spark.examples.SparkPi /spark/spark-2.4.4-bin-hadoop2.7/examples/jars/spark-examples_2.11-2.4.4.jar 100
#指定--master spark://hadoop000:7077 则为standalone集群模式
#会出现在webUI监控界面
[root@hadoop000 bin]# spark-submit --master spark://hadoop000:7077 --class org.apache.spark.examples.SparkPi /spark/spark-2.4.4-bin-hadoop2.7/examples/jars/spark-examples_2.11-2.4.4.jar 100
spark-shell 交互式命令行的基本操作
wordcount-shell实现
先起一个集群模式的shell进程:
[root@hadoop000 bin]# spark-shell --master spark://hadoop000:7077
wordcount思路:
SparkContext是spark程序的入口.写spark程序必须先有SparkContext实例。
在sparkshell交互式命令中,已经创建好SparkContext,sc.
在集群模式下读取文件,优先使用HDFS,否则只能当所有worker节点都存在该文件时或使用local模式才不报错
因此上传文件到hdfs:
touch /hadoop/wc.txt
fs -mkdir -p /wordcount/input
hadoop fs -put /hadoop/wc.txt /wordcount/input
hadoop fs -put /hadoop/wc.txt /wordcount/input/a.txt
hadoop fs -put /hadoop/wc.txt /wordcount/input/b.txt
#spark的转化类的算子是懒加载的,遇到action才会触发任务执行
scala> val rdd = sc.textFile("hdfs://hadoop000:8020/wordcount/input")
rdd: org.apache.spark.rdd.RDD[String] = hdfs://hadoop000:8020/wordcount/input MapPartitionsRDD[1] at textFile at <console>:24
scala> val rdd2 = rdd.flatMap(_.sp)
span split splitAt
scala> val rdd2 = rdd.flatMap(_.split(" "))
rdd2: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[2] at flatMap at <console>:25
scala> val rdd3 = rdd2.map((_,1))
rdd3: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[3] at map at <console>:2
scala> val rdd4 = rdd3.reduceByKey(_+_)
rdd4: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[4] at reduceByKey at <console>:25
scala> rdd4.collect
res1: Array[(String, Int)] = Array((tom,9), (hello,15), (jim,9), (spark,3))
scala> val rdd5=rdd4.sortBy(-_._2)
rdd5: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[9] at sortBy at <console>:25
转至hadoop:
[root@hadoop000 sbin]# hadoop fs -ls /wordcount/output1
Found 4 items
-rw-r--r-- 3 root supergroup 0 2019-10-22 15:46 /wordcount/output1/_SUCCESS
-rw-r--r-- 3 root supergroup 27 2019-10-22 15:46 /wordcount/output1/part-00000
-rw-r--r-- 3 root supergroup 10 2019-10-22 15:46 /wordcount/output1/part-00001
-rw-r--r-- 3 root supergroup 0 2019-10-22 15:46 /wordcount/output1/part-00002
[root@hadoop000 sbin]# hadoop fs -cat /wordcount/output1/part-00000
(hello,15)
(tom,9)
(jim,9)
转换类的算子:flatMap,map,reduceByKey,sortBy lazy执行,生成新的rdd
action类的算子:collect,saveAsTestFile
IDEA编程,提交至集群中运行
代码:
package com.qwx.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object WordCount {
def main(args: Array[String]): Unit = {
if(args.length != 2){
println("Usage:com.qwx.spark.WordCount <input><output>")
sys.exit(1)
}
val Array(input,output)= args
val conf = new SparkConf()
//SparkContext
val sc:SparkContext = new SparkContext(conf)
//sc.textFile("").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).sortBy(_._2).saveAsTextFile("")
//读取文件
val rdd1: RDD[String] = sc.textFile(input)
//切分并压平
val wordsrdd: RDD[String] = rdd1.flatMap(_.split(" "))
//组装
val wordAndOne: RDD[(String,Int)] = wordsrdd.map((_,1))
//分组聚合
val result: RDD[(String,Int)] = wordAndOne.reduceByKey(_+_)
//排序 制定降序
val sort = result.sortBy(-_._2)
//存储
sort.saveAsTextFile(output)
//释放资源
sc.stop()
}
}
打包到集群节点上
[root@hadoop000 sbin]# spark-submit --master spark://hadoop000:7077 --class com.qwx.spark.WordCount /spark/spark-1.0-SNAPSHOT.jar hdfs://hadoop000:8020/wordcount/input hdfs://hadoop000:8020/wordcount/output2
但有如下错误:
19/10/22 17:39:02 WARN TaskSchedulerImpl: Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient resources
原因:shell没有关掉,将spark集群资源完全占用,任务分不到资源。
查看运行结果:
[root@hadoop000 sbin]# hadoop fs -cat /wordcount/output2/part-00000
spark本地模式运行(方便测试)
//本地模式需要的配置:
val conf = new SparkConf()
.setMaster("local[*]")
.setAppName(this.getClass.getSimpleName)
worker资源:
默认机器的全部cores,memory默认是可用内存-1 可在spark-env.sh中修改
spark Java版本的wordcount
public class JavaWordCountLocal {
public static void main(String[] args) {
if(args.length != 2){
System.out.println("Usage:com.qwx.spark.WordCount <input><output>");
}
SparkConf sparkConf = new SparkConf();
sparkConf.setMaster("local[*]");
sparkConf.setAppName(JavaWordCountLocal.class.getSimpleName());
//java api程序的入口
JavaSparkContext sc = new JavaSparkContext(sparkConf);
//读取数据
JavaRDD<String> lines = sc.textFile(args[0]);
//切分并压平 输入参数,输出参数
JavaRDD<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});
//组装 单词 和 1 输入 key value
JavaPairRDD<String, Integer> wordWithOne = words.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
//分组聚合 (a,b) => a+b a b 返回值
JavaPairRDD<String, Integer> result = wordWithOne.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) throws Exception {
return i1 + i2;
}
});
//排序 上层输出作为本层输入 输出key value
//1.k - v反转
JavaPairRDD<Integer, String> reverseRes = result.mapToPair(new PairFunction<Tuple2<String, Integer>, Integer, String>() {
@Override
public Tuple2<Integer, String> call(Tuple2<String, Integer> stringIntegerTuple2) throws Exception {
return stringIntegerTuple2.swap();
}
});
//默认的规则是升序 降序
JavaPairRDD<Integer, String> sortRes = reverseRes.sortByKey(false);
//再反转 上层输出作为本层输入 输出key value
JavaPairRDD<String, Integer> finalRes = sortRes.mapToPair(new PairFunction<Tuple2<Integer, String>, String, Integer>() {
@Override
public Tuple2<String, Integer> call(Tuple2<Integer, String> integerStringTuple2) throws Exception {
return integerStringTuple2.swap();
}
});
finalRes.saveAsTextFile(args[1]);
sc.stop();
}
}
java -lambda wordcount
public class WordCount {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
JavaSparkContext sc = new JavaSparkContext();
JavaRDD<String> lines = sc.textFile(args[0]);
JavaRDD<String> words = lines.flatMap(t -> Arrays.asList(t.split(" ")).iterator());
JavaPairRDD<Integer, Integer> wordWithOne = words.mapToPair(w -> new Tuple2<>(2, 1));
JavaPairRDD<Integer, Integer> reduceData = wordWithOne.reduceByKey((a, b) -> a + b);
JavaPairRDD<Integer, Integer> swapData = reduceData.mapToPair(tp -> tp.swap());
JavaPairRDD<Integer, Integer> sortData = swapData.sortByKey(false);
JavaPairRDD<Integer, Integer> result = sortData.mapToPair(tp -> tp.swap());
result.saveAsTextFile(args[1]);
sc.stop();
}
}
spark的基本运行机制
执行spark程序时,产生的进程:
Master:集群的管理者,管理worker,接收客户端的任务请求
Worker:worker定时像master发送心跳,接收master的任务指令,管理自己节点上的Executor(任务真正运行的地方)
当执行spark任务时产生CoarseGrainedExecutorBackend和SparkSubmit,当任务执行结束之后,两进程退出
CoarseGrainedExecutorBackend:称之为executor就是spark任务真正运行的地方
SparkSubmit:任务提交产生的进程
spark的资源分配
Spark任务执行时,默认的资源分配是:分配给executor的资源。
executor的cores:standalone模式下worker上的所有cores,yarn模式1cores
executor的memory:默认为1G
在spark-submit中可进行DIY:
- -executor -cores 2 每个executor使用的cores
- -executor -memory 2g 最低配置是450M 每个executor
- -total-executor-cores 总的cores 最高的标准
3个worker 4cores 2.7g 默认一个worker启动一个executor
- -executor -cores 2 - -executor -memory 2g 启动3个executor
- -executor -cores 2 - -executor -memory 1g 启动6个executor
- -executor -cores 2 - -executor -memory 1g - -total-executor-cores 启动2个executor
可验证:
spark-shell --master spark://hadoop000:7077 --executor-co-executor-memory 1g
spark-submit/spark-shell 选项 jar包 参数
常用选项:
–master 任务运行的模式
–class 主类
–name 任务运行的名称
–jars 任务运行依赖的第三方jar包
–executor -cores
–executor -memory
–total -executor-cores
RDD
RDD是什么?
RDD是Spark编程的基本抽象
RDD(Resilient Distributed DataSet):弹性分布式数据集合
不可变,只读的,被分区的数据集
抽象的集合,使用上而言,与本地集合的差别不大,也有方法可调用,无需关心底层调用。
如何创建RDD
- 三种方式:
- 读取文件系统
local模式下,读取本地文件:sc.textFile("/root/abc.txt")
# spark-shell 默认即为local模式
常用方式:读取hdfs文件:scala> val rdd = sc.textFile(“hdfs://hadoop000:8088/wordcount”) - 集合并行化(生产环境不常用,常用于测试)
scala的本地集合转化为RDD,两种方式,两种方式本是同一个api
转化时不指定分区,默认一个core就能处理一个分区的数据。
手动指定分区数量:sc.makeRDD(list,2)
- 读取文件系统
1 - makeRDD:
scala> val list = List(String,Int)
list: List[(String, Int)] = List((a,1))
scala> val rdd3 = sc.makeRDD(list)
rdd3: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[3] at makeRDD at <console>:26
2 - parallelize:
scala> var arr2=List(Array(3))
arr2: List[Array[Int]] = List(Array(3))
scala> var rdd5 = sc.parallelize(arr2)
rdd5: org.apache.spark.rdd.RDD[Array[Int]] = ParallelCollectionRDD[5] at parallelize at :26
- RDD之间的转换(Transformation算子)
转换类的算子会生成新的RDD
scala> val rdd = sc.textFile(“hdfs://hadoop000:8088/wordcount”)
rdd: org.apache.spark.rdd.RDD[String] = hdfs://hadoop000:8088/wordcount MapPartitionsRDD[7] at textFile at <console>:24
scala> val rdd2 = rdd.map((_,1))
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[8] at map at <console>:25
RDD的分区
RDD的分区数,能决定spark任务的并行度。
可以在rdd上调用rdd.partitions.size获取rdd的分区数量
读取外部文件分区
scala> val rdd = sc.textFile("hdfs://hadoop000:8020/wordcount/input")
rdd: org.apache.spark.rdd.RDD[String] = hdfs://hadoop000:8020/wordcount/input MapPartitionsRDD[12] at textFile at <console>:24
scala> rdd.partitions.size
res1: Int = 3
默认情况下,读取HDFS的文件,生成的rdd的分区数量,默认和文件的block块的文件数量一致。
但如果文件只有一个block块,至少有两个分区。
reparation算子可以用于修改分区数量。
scala> val rdd = sc.textFile(“hdfs://hadoop000:8020/wordcount/input”,1)
rdd: org.apache.spark.rdd.RDD[String] = hdfs://hadoop000:8020/wordcount/input MapPartitionsRDD[14] at textFile at <console>:24
scala> rdd.partitions.size
res2: Int = 3
scala> val rdd = sc.textFile(“hdfs://hadoop000:8020/wordcount/input”,8)
rdd: org.apache.spark.rdd.RDD[String] = hdfs://hadoop000:8020/wordcount/input MapPartitionsRDD[16] at textFile at <console>:24
scala> rdd.partitions.size
res3: Int = 9
补充:修改的分区数量,不能少于数据的block块的数量。修改的分区数量更大,则会重新分区,因此设置的8,返回的却是9.
分区数量 = 文件的block块数量 >=2 ? block块的数量 : 2
RDD之间的转换:
父RDD --> 子RDD 默认情况下,分区的数量不变
reduceByKey(_+_,2) 可以显式地改变分区数量
map,flatMap 不能改变分区数量
repartition,union 可能会扩大分区数量
coalesce 可能会减少分区数量
RDD的算子
把RDD这个集合上的所有方法称为算子
综述:算子是RDD中定义的方法,分为转换(transformation)和动作(action)。Transformation算子并不会触发spark提交作业,直至action算子才算提交任务执行,这是一个延迟计算的设计技巧,可以避免内存过快被中间计算沾满,从而提高内存利用率。
RDD拥有的操作比MapReduce丰富的多,不仅仅包括Map,Reduce操作,还包括filter,sort,join,save,count等操作,中间结果不需要保存,所以Spark比MapReduce更容易方便完成更复杂的任务。
RDD支持两种类型的操作:
- 转换(transformation)现有的RDD通过转换生成一个新的RDD。lazy模式,延迟执行。转换函数包括:map,filter,flatMap,groupByKey,reduceByKey,aggregateByKey,union,join,coalesce等等。
- 动作:在RDD上运行计算,并返回结果给驱动程序(Driver)或写入文件系统。动作操作包括:reduce,collect,count,first,take,countByKey以及foreach等。
action真正触发spark的任务执行,此时返回的类型不再是RDD,数据就会存储到指定的文件系统中(展示在executor中),或者直接打印结果或收集(展示在driver端)
Transformation
RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果,相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作,只有当发生一个要求返回的结果给Driver的动作时,这些转换才会真正运行。这种设计让Spark更加有效率地运行。对RDD中元素执行的操作,实际上就是对RDD中的每一个分区的数据进行操作,而不需要关心数据在那个分区中。
map mapValues mapPartitions mapPartitionsWithIndex
map:一一映射,对RDD中每一条数据执行某一项操作
迭代次数 = 数据的条数
scala> val rdd1 = sc.makeRDD(List(1,2,3,4,5,6),3)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[17] at makeRDD at <console>:24
scala> val rdd2 = rdd1.map(_*10)
rdd2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[18] at map at <console>:25
scala> rdd2.collect
res8: Array[Int] = Array(10, 20, 30, 40, 50, 60)
mapValues:一一映射,只针对于RDD[K,V] 作用于V, K不变
scala> val rdd3 = sc.makeRDD(Array(("a",1),("b",2)))
rdd3: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[19] at makeRDD at <console>:24
scala> val rdd4 = rdd3.mapValues(_*10)
rdd4: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[20] at mapValues at <console>:25
scala> rdd4.collect
res7: Array[(String, Int)] = Array((a,10), (b,20))
mapPartitions :针对rdd中的每个分区的数据进行处理
迭代次数 = 分区的数量
mapPartitionsWithIndex
每次迭代一个分区的数据 + 分区的编号
分区的编号从0开始
object MapDemo {
def main(args: Array[String]): Unit = {
val sc = MySparkUtil(this.getClass.getSimpleName)
val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),3)
rdd1.map(_*10)
//每次迭代一个分区
rdd1.mapPartitions(it=> it.map(_*10))
val f = (i:Int,it:Iterator[Int]) => {
it.map(t=> s"v=$t,p=$i")
}
//看数据在哪一个分区
val index = rdd1.mapPartitionsWithIndex(f)
val collect: Array[String] = index.collect()
println(collect.toBuffer)
//输出:ArrayBuffer(v=1,p=0, v=2,p=0, v=3,p=1, v=4,p=1, v=5,p=2, v=6,p=2)
sc.stop()
}
}
不改变分区数量
flatMap
map+flatten:有嵌套集合的时候用flatMap
不会改变分区数量
filter:
过滤出满足条件的所有元素。
def filter(f: T => Boolean):RDD[T]
scala> val rdd1 = sc.makeRDD(List(1,2,3,4,5,6),3)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at makeRDD at <console>:24
scala> val rdd2 = rdd1.filter(_>4)
rdd2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[1] at filter at <console>:25
scala> val f=(i:Int,it:Iterator[Int])=> it.map(t=>s"v=$t,p=$i")
f: (Int, Iterator[Int]) => Iterator[String] = <function2>
scala> rdd2.mapPartitions
mapPartitions mapPartitionsWithIndex
scala> rdd2.mapPartitionsWithIndex(f).collect
res0: Array[String] = Array(v=5,p=2, v=6,p=2)
程序中:
//由于filter是重量级算子,在写过滤条件时尽量将其写在一个filter中
rdd1.filter(t=>{
t>5 && t%2 ==0
})
filter不能改变分区数量,即使分区被过滤掉。
集合的交集并集差集
union intersection subtract
union:
scala> val rdd1 = sc.makeRDD(List(1,2,3,4),2)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at makeRDD at <console>:24
scala> val rdd2 = sc.makeRDD(List(4,5,6,7),3)
rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at makeRDD at <console>:24
scala> val unionrdd = rdd1.union(rdd2)
unionrdd: org.apache.spark.rdd.RDD[Int] = UnionRDD[2] at union at <console>:27
scala> unionrdd.partitions.size
res0: Int = 5
分区相加,属于轻量级的算子
scala> (rdd1 intersection rdd2).collect
res3: Array[Int] = Array(4)
scala> (rdd1 subtract rdd2).collect
res5: Array[Int] = Array(2, 1, 3)
distinct
scala> unionrdd.collect
res8: Array[Int] = Array(1, 2, 3, 4, 4, 5, 6, 7)
scala> (unionrdd.distinct).collect
res9: Array[Int] = Array(5, 1, 6, 7, 2, 3, 4)
scala> val disrdd = unionrdd.distinct(3)
scala> disrdd。partitions.size
res12: Int = 3
调用了reduceByKey实现了去重
goupBy goupByKey reduceByKey
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
相同点:
三个算子,默认分区数量都不变,但是都是可以显式地传递一个分区数量,分区器
groupByKey和reduceByKey的异同:
reduceByKey必须传递一个聚合函数,groupByKey不需要
reduceByKey会进行局部聚合,性能较高。
groupByKey:
reduceByKey:
object GroupByKeyDemo {
def main(args: Array[String]): Unit = {
val sc: SparkContext = MySparkUtil(getClass.getSimpleName)
//groupBy 根据指定条件进行分组 key是由自定义函数的返回值类型决定的
//RDD[K] RDD[K,V]-
val rdd1 = sc.makeRDD(List("reba","baby","nazha","dr","nazha"))
val rdd2 = sc.makeRDD(List(10,20,30))
val by: RDD[(String, Iterable[String])] = rdd1.groupBy(t => t)
val by1: RDD[(String, Iterable[Int])] = rdd2.groupBy(t=>t.toString)
//利用groupBy实现wordcount
val words: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("b",1)("a",1),("d",1)))
val grouped1 = words.groupBy(_._1)
val result1 = grouped1.mapValues(t=>t.map(_._2).sum)
//groupByKey 只能按key RDD[K,V]
val group2: RDD[(String, Iterable[Int])] = words.groupByKey(3)
val result2: RDD[(String, Int)] = group2.mapValues(_.sum)
//reduceByKey RDD[K,V]
val result3: RDD[(String, Int)] = words.reduceByKey(_+_)
}
}
Action类的算子
foreach:和map类型相似,一一映射,对每条数据执行操作,返回值类型不同,foreach返回的Unit
scala> disRdd.collect()
res9: Array[Int] = Array(5, 1, 6, 7, 2, 3, 4)
scala> disRdd.foreach(println)
foreach的输出结果在executor中
foreachPartition:作用于每一个分区,每次触发action执行时,最终的task,会在不同的机器上运行。
map,mapPartitions,foreach,foreachPartition的区别:
map和foreach:
都是对集合的每一条数据执行操作。
map是转换算子,生成RDD,foreach是action算子,执行结果。
map有返回值类型,foreach不需要有返回值(Unit)
reduce(func) 通过func函数聚集RDD中的所有元素
collect() 在驱动程序中,以数组的形式返回数据集的所有元素,会把数据收集到driver,数据过多会OOM;返回的结果是有序的
collectAsMap() 类似于collect,该函数用于pairRDD,最终返回map类型的结果
count() 返回RDD的元素个数
first() 返回RDD的第一个元素(类似于take(1))
take(n) 返回一个由数据集的前n个元素组成的数组
top(n) 默认按降序取数据
takeOrdered(n,[ordering]) 与top类似,顺序相反,默认升序
contByKey() 针对(k,v)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素。
foreach(func) 在数据集的每一个元素上,运行函数func进行更新,foreach任务在executor中运行,打印信息也会在executor中显示
foreachPartition 对分区进行操作。
scala> rdd2.collect
res6: Array[String] = Array(a, b, c, d)
scala> val rdd3 = rdd2.zipWithIndex
rdd3: org.apache.spark.rdd.RDD[(String, Long)] = ZippedWithIndexRDD[4] at zipWithIndex at <console>:25
scala> rdd3.collectAsMap
res5: scala.collection.Map[String,Long] = Map(b -> 1, d -> 3, a -> 0, c -> 2)
action算子会产生job,返回到driver端,正常情况下一个action算子会产生一个job。
collect流程分析::
如何将数据写入mysql?
使用map的话,则必须要调用action算子,每条数据都得获取一个连接
使用mapPartitions的话, 每个分区都得获取一条连接
foreach:每条数据都得获取一个连接
foreachPartition:每个分区都得获取一条连接
优选foreachPartition;
不推荐先collect到driver在写入数据库。可能会出现OOM。
计算平均温度demo
object TempDemo {
def main(args: Array[String]): Unit = {
val d1 = Array(("bj",28.1),("sh",28.7),("gz",32.0),("sz",33.1))
val d2 = Array(("bj",27.3),("sh",30.1),("gz",33.3))
val d3 = Array(("bj",28.2),("sh",29.1),("gz",32.0),("sz",30.5))
val sc = MySparkUtil(getClass.getSimpleName)
var data = d1 ++ d2 ++ d3
val rdd1: RDD[(String, Double)] = sc.makeRDD(data)
//groupBy实现
val groupByed: RDD[(String, Iterable[(String, Double)])] = rdd1.groupBy(_._1)
val groupByRes: RDD[(String, Double)] = groupByed.mapValues(t=>t.map(_._2).sum / t.size)
groupByRes.foreach(println)
//groupByKey实现
val groupedRdd: RDD[(String, Iterable[Double])] = rdd1.groupByKey()
val groupedResult: RDD[(String, Double)] = groupedRdd.mapValues(it=> it.sum / it.size)
groupedResult.foreach(println)
//reduceByKey实现
val rdd2: RDD[(String, List[Double])] = rdd1.mapValues(List(_))
val reduceRdd: RDD[(String, List[Double])] = rdd2.reduceByKey(_++_)
val reducedRes: RDD[(String, Double)] = reduceRdd.mapValues(t=>(t.sum / t.size))
reducedRes.foreach(println)
sc.stop()
}
}
zip和zipWithIndex
19/10/23 17:23:10 WARN TaskSetManager: Lost task 0.0 in stage 14.0 (TID 48, 192.168.100.183, executor 0): org.apache.spark.SparkException: Can only zip RDDs with same number of elements in each partition
java.lang.IllegalArgumentException: Can’t zip RDDs with unequal numbers of partitions: List(3, 4)
使用zip时,必须保证参与zip的集合,每一个分区的数据是一致的。而且分区数量一致
scala> val rdd1 = sc.makeRDD(List("zxy","ldh","jl"),3)
rdd1: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[22] at makeRDD at <console>:24
scala> val rdd2 = sc.makeRDD(List(1000,2000,100000),3)
rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[23] at makeRDD at <console>:24
scala> val rdd3 = rdd1 zip rdd2
rdd3: org.apache.spark.rdd.RDD[(String, Int)] = ZippedPartitionsRDD2[24] at zip at <console>:27
scala> rdd3.collect
res14: Array[(String, Int)] = Array((zxy,1000), (ldh,2000), (jl,100000))
scala> rdd3.partitions.size
res15: Int = 3
得到带编号的用zipWithIndex
scala> rdd3.zipWithIndex
res16: org.apache.spark.rdd.RDD[((String, Int), Long)] = ZippedWithIndexRDD[25] at zipWithIndex at <console>:26
scala> res16.collect
res17: Array[((String, Int), Long)] = Array(((zxy,1000),0), ((ldh,2000),1), ((jl,100000),2))
排序:sortBy和sortByKey
sortBy:可作用于RDD[K] RDD[K,V] 根据指定的条件进行排序
sortByKey:只能作用于RDD[K,V] 按key来排序
sortBy底层调用的是sortByKey
sortByKey调用的分区器是RangePartitioner,会产生一个job
object SortDemo {
def main(args: Array[String]): Unit = {
val sc = MySparkUtil(this.getClass.getSimpleName)
val rdd: RDD[Int] = sc.makeRDD(List(11,2,3,9,1),1)
//排序, 单独写_不能被解析成函数 +_:升序 -_:降序 可以用 t=>t
//foreach无法保证顺序,使用coalesce() 函数将分区转为1 个分区,确保打印出来 有序
//rdd.sortBy(+_).coalesce(1).foreach(println)
rdd.sortBy(+_).foreach(println)
val rdd2: RDD[(String, Int)] = sc.makeRDD(List(("xy",2000),("jl",100),("fge",10000)))
//false:降序
rdd2.map(_.swap).sortByKey(false).coalesce(1).foreach(println)
rdd2.sortBy(-_._2).coalesce(1).foreach(println)
sc.stop()
}
}
两个可改变分区的算子repartition和coalesce
repartition(10)=coalesce(10,true)
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
coalesce(numPartitions, shuffle = true)
}
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
shuffle:数据是否重新分发 即:一个分区的数据是否会被分配到多个分区中
repartition:重新分区 数据必须进行shuffle
coalesce:常用于减少合并分区,数据不会进行shuffle,除非传入参数为true
查看分区数据
scala> val rdd2 = sc.makeRDD(List(1000,2000,100000),3)
rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[23] at makeRDD at <console>:24
scala> val f = (i:Int,it:Iterator[Int])=>it.map(t=>s"part=$i,value=$t")
f: (Int, Iterator[Int]) => Iterator[String] = <function2>
scala> rdd2.mapPartitionsWithIndex(f).collect
res18: Array[String] = Array(part=0,value=1000, part=1,value=2000, part=2,value=100000)
如果需要对数据进行重新分发(shuffle),优选repartition
仅仅需要合并分区,不需要数据的shuffle,优先选择coalesce。
coalesce 不能用于扩大分区,否则分区数量是不变的。
repartition常用于扩大分区,把分区数量调大,分区内的数量减少,并发量更大,处理速度更快
join
必须作用于RDD[K,V]
相当于SQL中的内关联join,只返回两个RDD根据K可以关联上的结果
在类型为[K,V]和[K,W]类型的数据集上调用时,返回一个相同key对应所有元素对在一起的(K,(V,W))数据集
*1. 只返回根据key可以关联上的数据,关联不上直接舍去
*2. [K,V] join [K,W] 返回值 [K,(V,W)]
scala> val rdd1 = sc.makeRDD(List(("zxy",100.6),("ldh",120.0),("jl",1000.5)))
rdd1: org.apache.spark.rdd.RDD[(String, Double)] = ParallelCollectionRDD[1] at makeRDD at <console>:24
scala> val rdd2 = sc.makeRDD(List(("zxy",3),("ldh",4),("jl",10),("rh",2)))
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[2] at makeRDD at <console>:24
scala> val rdd3 = rdd1 join rdd2
rdd3: org.apache.spark.rdd.RDD[(String, (Double, Int))] = MapPartitionsRDD[5] at join at <console>:27
scala> rdd3.collect
res0: Array[(String, (Double, Int))] = Array((ldh,(120.0,4)), (jl,(1000.5,10)), (zxy,(100.6,3)))
scala> rdd3.mapValues(t=>t._1*t._2).collect
res4: Array[(String, Double)] = Array((ldh,480.0), (jl,10005.0), (zxy,301.79999999999995))
join [K,(V,W)]
leftOuterJoin [K,(V,OptionW)]
RightOuterJoin [K,(OptionV,W)]
FullOuterJoin [K,(OptionV,OptionW)]
scala> val rdd1 = sc.makeRDD(List(("zxy",100.6),("ldh",120.0),("jl",1000.5),("zs",10.0)))
rdd1: org.apache.spark.rdd.RDD[(String, Double)] = ParallelCollectionRDD[8] at makeRDD at <console>:24
scala> val rdd2 = sc.makeRDD(List(("zxy",3),("ldh",4),("jl",10),("rh",2)))
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[2] at makeRDD at <console>:24
scala> val rdd4 = rdd1 leftOuterJoin rdd2
rdd4: org.apache.spark.rdd.RDD[(String, (Double, Option[Int]))] = MapPartitionsRDD[11] at leftOuterJoin at <console>:27
scala> rdd4.collect
res5: Array[(String, (Double, Option[Int]))] = Array((ldh,(120.0,Some(4))), (zs,(10.0,None)), (jl,(1000.5,Some(10))), (zxy,(100.6,Some(3))))
scala> rdd4.mapValues(t=>(t._1 * t._2.getOrElse(0))).collect
res6: Array[(String, Double)] = Array((ldh,480.0), (zs,0.0), (jl,10005.0), (zxy,301.79999999999995))
cogroup
在类型为[K,V]和[K,W]的数据集上调用,返回一个(K,(Iterable[V],Interable[W]))元组的数据集,这个操作也可以称为groupwith,相当于SQL中的全外连接full outer join,返回左右RDD中的记录,关联不上的为空
full outer join 和 cogroup对比:
当RDD/集合中有相同的元素,用cogroup可自动聚合,免去使用full outer join还要使用reduceByKey进行聚合
scala> val rdd1 = sc.makeRDD(List(("zxy",100.6),("ldh",120.0),("ldh",100.0)))rdd1: org.apache.spark.rdd.RDD[(String, Double)] = ParallelCollectionRDD[15] at makeRDD at <console>:24
scala> val rdd2 = sc.makeRDD(List(("zxy",3),("ldh",4)))rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[16] at makeRDD at <console>:24
scala> val rdd3 = rdd1 fullOuterJoin rdd2
rdd3: org.apache.spark.rdd.RDD[(String, (Option[Double], Option[Int]))] = MapPartitionsRDD[19] at fullOuterJoin at <console>:27
scala> rdd3.collect
res8: Array[(String, (Option[Double], Option[Int]))] = Array((ldh,(Some(120.0),Some(4))), (ldh,(Some(100.0),Some(4))), (zxy,(Some(100.6),Some(3))))
scala> val rdd4 = rdd1 cogroup rdd2
rdd4: org.apache.spark.rdd.RDD[(String, (Iterable[Double], Iterable[Int]))] = MapPartitionsRDD[21] at cogroup at <console>:27
scala> rdd4.collect
res9: Array[(String, (Iterable[Double], Iterable[Int]))] = Array((ldh,(CompactBuffer(120.0, 100.0),CompactBuffer(4))), (zxy,(CompactBuffer(100.6),CompactBuffer(3))))
scala> rdd4.mapValues(tp=>tp._1.sum * tp._2.sum).collect
res11: Array[(String, Double)] = Array((ldh,880.0), (zxy,301.79999999999995))
cartesian笛卡尔积
在类型为T和U类型的数据集上调用时,返回一个(T,U)对数据集(两两的元素对)
scala> val rdd1 = sc.makeRDD(List(("zxy",100.6),("ldh",120.0),("ldh",100.0)))rdd1: org.apache.spark.rdd.RDD[(String, Double)] = ParallelCollectionRDD[15] at makeRDD at <console>:24
scala> val rdd2 = sc.makeRDD(List(("zxy",3),("ldh",4)))rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[16] at makeRDD at <console>:24
scala> var rdd3 = rdd1 cartesian rdd2
rdd3: org.apache.spark.rdd.RDD[((String, Double), (String, Int))] = CartesianRDD[24] at cartesian at <console>:27
scala> rdd3.collect
res12: Array[((String, Double), (String, Int))] = Array(((zxy,100.6),(zxy,3)), ((zxy,100.6),(ldh,4)), ((ldh,120.0),(zxy,3)), ((ldh,120.0),(ldh,4)), ((ldh,100.0),(zxy,3)), ((ldh,100.0),(ldh,4)))
aggregate和aggregateByKey
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
action类的算子,直接返回结果
aggregate用于聚合RDD中的元素,先使用seqOp将RDD中每个分区中的T类型元素聚合成U类型。
再使用combOp将之前每个分区聚合后的U类型聚合成U类型,特别注意seqOp和combOP都会使用zeroValue的值,zeroValue的类型为U
第一个参数:初始值
第二个参数列表中,第一个参数,局部聚合函数(分区内聚合),第二个参数:全局聚合
初始值参与分区运算,还参与全局运算
object AggregateDemo {
def main(args: Array[String]): Unit = {
val sc = MySparkUtil(this.getClass.getSimpleName)
val rdd1 = sc.makeRDD(List(List(1,3),List(2,4),List(3,5)),3)
//a:累加值(zeroValue) b:元素值
val res1: Int = rdd1.aggregate(0)((a,b)=>a+b.max,((a,b)=>a+b))
val res2: Int = rdd1.aggregate(0)((a,b)=>a+b.sum,((a,b)=>a+b))
val res3: Int = rdd1.aggregate(0)(_+_.sum,_+_)
}
}
aggregateByKey:
转换类的算子
第一个参数:初始值
第二个参数列表中,第一个参数,局部聚合函数(分区内聚合),第二个参数:全局聚合
初始值只参与分区运算
scala> val pairRDD = sc.parallelize(List(("cat",2),("cat",5),("mouse",4),("cat",12),("dog",12),("mouse",12)),2)
pairRDD: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[0] at parallelize at <console>:24
scala> val pairRes = pairRDD.aggregateByKey(0)(_+_,_+_)
pairRes: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[1] at aggregateByKey at <console>:25
scala> pairRes.collect
res0: Array[(String, Int)] = Array((dog,12), (cat,19), (mouse,16))
combineByKeyWithClassTag是所有byKey类算子调用的底层算子
def combineByKeyWithClassTag[C](
//确定聚合值的类型,初始值(累加值)
createCombiner: V => C,
//分区内聚合
mergeValue: (C, V) => C,
//全局聚合
mergeCombiners: (C, C) => C,
partitioner: Partitioner,
mapSideCombine: Boolean = true,
serializer: Serializer = null)(implicit ct: ClassTag[C]): RDD[(K, C)]
RDD算子的五大特性
源码中对RDD5大特性的说明:
Internally, each RDD is characterized by five main properties:
- A list of partitions
- A function for computing each split
- A list of dependencies on other RDDs
- Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
- Optionally, a list of preferred locations to compute each split on (e.g. block locations for
an HDFS file)
5大特性分别对应着RDD的方法或属性
- 一个数组,里面的类型是Partition
/**
* Implemented by subclasses to return the set of partitions in this RDD. This method will only
* be called once, so it is safe to implement a time-consuming computation in it.
*
* The partitions in this array must satisfy the following property:
* `rdd.partitions.zipWithIndex.forall { case (partition, index) => partition.index == index }`
*/
protected def getPartitions: Array[Partition]
- compute计算函数
/**
* :: DeveloperApi ::
* Implemented by subclasses to compute a given partition.
*/
@DeveloperApi
def compute(split: Partition, context: TaskContext): Iterator[T]
返回值类型:Iterator
负责RDD之间的计算,从父RDD到子RDD之间的数据转换
//示例
val rdd1 = sc.makeRDD(List(1,2,3,4,5,6),3)
rdd.map(_*10)
//map源码:
//new了一个MapPartitionsRDD
override def compute(split: Partition, context: TaskContext): Iterator[U] =
f(context, split.index, firstParent[T].iterator(split, context))
f:传递的函数 _*10
把父RDD的每一个分区的数据,封装成Iterator,传入到新的函数中,得到一个计算之后的结果数据,依然封装在Iterator。
注意事项:
-没有父RDD的RDD,compute就是依据自己的数据,新建一个Iterator
-shuffle类算子,需要把数据写入到磁盘中(业务逻辑较复杂)
- 依赖关系:
父RDD和子RDD之间的依赖关系。(父子RDD之间分区的依赖关系)
/**
* Implemented by subclasses to return how this RDD depends on parent RDDs. This method will only
* be called once, so it is safe to implement a time-consuming computation in it.
*/
protected def getDependencies: Seq[Dependency[_]] = deps
-
可选,分区器
只有RDD[K,V]类型才有分区器,默认的分区器是HashPartitioner。
Spark中提供了两种分区,HashPartitioner和RangePartitioner
RDD[Int] 分区器是None
reduceByKey ,groupByKey ,等 默认分区都是HashPartitioner
sortByKey , sort ,用的是RangePartitioner
HashPartitioner:key的hashCode % 分区数量
RangePartitioner:是按照key的区间,然后按照key对应的values数量来尽可能地平均 -
优先位置:
spark中理念:移动数据,不如移动计算
在有数据的节点上启动计算任务,运行效率更高
--------------- 5大特性:常用
分区器:自定义分区器
分区列表:修改分区的数量
依赖关系,compute,优先位置,不做任何操作
wordcount为例,spark的简单运行流程
val sc:SparkContext = new SparkContext(conf)
//读取文件
val rdd1: RDD[String] = sc.textFile(input)
//切分并压平
val wordsrdd: RDD[String] = rdd1.flatMap(_.split(" "))
//组装
val wordAndOne: RDD[(String,Int)] = wordsrdd.map((_,1))
//分组聚合
val result: RDD[(String,Int)] = wordAndOne.reduceByKey(_+_)
//排序 制定降序
val sort = result.sortBy(-_._2)
//展示RDD间的依赖关系
println(sort.toDebugString)
//存储
sort.saveAsTextFile(output)
shell–wordCount
scala> val rdd1 = sc.textFile("hdfs://hadoop000:8020/wordcount/input")
rdd1: org.apache.spark.rdd.RDD[String] = hdfs://hadoop000:8020/wordcount/input MapPartitionsRDD[1] at textFile at <console>:24
scala> val rdd2 = rdd1.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[4] at reduceByKey at <console>:25
scala> rdd2.saveAsTextFile("hdfs://hadoop000:8020/wordcount/output5")
DAG可视化图:
数据流向图,将数据流向从reduceByKey切分为两个阶段,只要有数据的shuffle(数据需要重新分发,就会产生stage(阶段))
spark任务的最小单位:task
Driver解析业务逻辑代码–》提交job–》切分stage–》组装task–》task分发给executor
executor:接收task,正式开始读数据 executor只是接收driver的task去处理
展示量和点击量demo
object ToggleNum {
def main(args: Array[String]): Unit = {
val sc = MySparkUtil(this.getClass.getSimpleName)
//读取数据
val file: RDD[String] = sc.textFile("D:\\zx.txt")
//数据切分
//1010,华语剧场|剧情|当代|类型,1,0
//flatMap用于嵌套map
val processData: RDD[((String, String), (Int, Int))] = file.flatMap(str => {
val split = str.split(",")
val id = split(0)
val words = split(1)
val imp = split(2).toInt
val click = split(3).toInt
//对关键词切分
val split1 = words.split("\\|")
split1.map(t => ((id, t), (imp, click)))
})
//processData.collect().foreach(println)
//简单方法获得
val result1: RDD[((String, String), (Int, Int))] = processData.reduceByKey {
case (v1, v2) => (v1._1 + v2._2, v2._1 + v1._2)
}
//分组聚合方法获得
//分组
val groupedRDD: RDD[((String, String), Iterable[(Int, Int)])] = processData.groupByKey()
//聚合
val result2: RDD[((String, String), (Int, Int))] = groupedRDD.mapValues(it => {
val totalTemp = it.map(_._1).sum
val totalClick = it.map(_._2).sum
(totalTemp, totalClick)
})
//整合处理结果
val value: RDD[(String, String, Int, Int)] = result2.map{case ((id, t), (imp, click)) => (id, t,imp, click)}
val sortValue: RDD[(String, String, Int, Int)] = value.sortBy(+_._4)
sortValue.foreach(println)
}
}
全局topKdemo
object TopK {
val topK = 2
def main(args: Array[String]): Unit = {
//数据格式:http://bigdata.edu360.cn/laozhang
val sc = MySparkUtil(this.getClass.getSimpleName)
val file: RDD[String] = sc.textFile("D:\\zc.txt")
//可用foreach测试
file.foreach(str=>{
val index: Int = str.lastIndexOf("/")
val tName: String = str.substring(index+1)
val url = new URL(str.substring(0,index))
val subject: String = url.getHost.split("\\.")(0)
//println(subject)
})
val rdd: RDD[((String, String), Int)] = file.map(str => {
val index: Int = str.lastIndexOf("/")
val tName: String = str.substring(index + 1)
val url = new URL(str.substring(0, index))
val subject: String = url.getHost.split("\\.")(0)
//封装成一个元组
((subject, tName), 1)
})
val result: RDD[((String, String), Int)] = rdd.reduceByKey(_+_)
//排序,按照访问次数降序
val finalRes: Array[((String, String), Int)] = result.sortBy(-_._2).take(topK)
finalRes.foreach(println)
sc.stop()
}
}
按学科分组topKdemo
/**
* 分学科的topK
*/
object SubTopK {
val topK = 3
def main(args: Array[String]): Unit = {
//数据格式:http://bigdata.edu360.cn/laozhang
val sc = MySparkUtil(this.getClass.getSimpleName)
val file: RDD[String] = sc.textFile("D:\\zc.txt")
//可用foreach测试
file.foreach(str=>{
val index: Int = str.lastIndexOf("/")
val tName: String = str.substring(index+1)
val url = new URL(str.substring(0,index))
val subject: String = url.getHost.split("\\.")(0)
//println(subject)
})
val rdd: RDD[((String, String), Int)] = file.map(str => {
val index: Int = str.lastIndexOf("/")
val tName: String = str.substring(index + 1)
val url = new URL(str.substring(0, index))
val subject: String = url.getHost.split("\\.")(0)
//封装成一个元组
((subject, tName), 1)
})
//按key处理
val result: RDD[((String, String), Int)] = rdd.reduceByKey(_+_)
//处理过的累加数据进行按学科分组
val groupedRes: RDD[(String, Iterable[((String, String), Int)])] = result.groupBy(it => {
it._1._1
})
//对每一个学科对应的 迭代器进行 排序
val finalRes: RDD[(String, List[(String, Int)])] = groupedRes.mapValues(it => {
//Iterator没有sortBy函数,将Iterator转为List,整理数据格式
//使用treeMap/treeSet 设置排序的规则是 按照次数降序 设置长度为topK
it.toList.sortBy(-_._2).map(it => (it._1._2, it._2)).take(topK)
})
finalRes.foreach(println)
sc.stop()
}
}
错误分析:
19/10/25 17:10:09 ERROR Executor: Exception in task 0.0 in stage 1.0 (TID 2)
org.apache.spark.SparkException: This RDD lacks a SparkContext. It could happen in the following cases:
(1) RDD transformations and actions are NOT invoked by the driver, but inside of other transformations; for example, rdd1.map(x => rdd2.values.count() * x) is invalid because the values transformation and count action cannot be performed inside of the rdd1.map transformation. For more information, see SPARK-5063.
(2) When a Spark Streaming job recovers from checkpoint, this exception will be hit if a reference to an RDD not defined by the streaming job is used in DStream operations. For more information, See SPARK-13758.
错误代码:
val subNames: RDD[String] = rdd.map(_._1._1).distinct
subNames.foreach(subName => {
//过滤出bigdata这个学科的数据
val bigSub: RDD[((String, String), Int)] = rdd.filter(t=>subName.equals(t._1._1))
//wordcount
val res: RDD[((String, String), Int)] = bigSub.reduceByKey(_+_)
//按访问次数排序
val finalRes: RDD[((String, String), Int)] = res.sortBy(_._2)
finalRes.foreach(println)
})
问题总结:
在一个RDD中操作了另一个RDD,RDD不能嵌套操作
改进方案:
/**
* 分组-分学科的topK
*/
object SubTopK2 {
def main(args: Array[String]): Unit = {
//数据格式:http://bigdata.edu360.cn/laozhang
val sc = MySparkUtil(this.getClass.getSimpleName)
val file: RDD[String] = sc.textFile("D:\\zc.txt")
val rdd: RDD[((String, String), Int)] = file.map(str => {
val index: Int = str.lastIndexOf("/")
val tName: String = str.substring(index + 1)
val url = new URL(str.substring(0, index))
val subject: String = url.getHost.split("\\.")(0)
//封装成一个元组
((subject, tName), 1)
})
val subNames: Array[String] = rdd.map(_._1._1).distinct().collect
for (subName <- subNames){
//过滤出bigdata这个学科的数据
val bigSub: RDD[((String, String), Int)] = rdd.filter(t=>subName.equals(t._1._1))
//wordcount
val res: RDD[((String, String), Int)] = bigSub.reduceByKey(_+_)
//按访问次数排序
val finalRes: RDD[((String, String), Int)] = res.sortBy(_._2)
finalRes.foreach(println)
}
sc.stop()
}
}
RDD的依赖关系
综述:RDD可以从本地集合并行化,从外部文件系统,也可以从其它的RDD得到。能从其它的RDD中得到的原因是rdd之间有依赖关系。
RDD和它依赖的父RDD的关系有两种不同的类型,及窄依赖(narrow dependency)和宽依赖(wide
dependency)
依赖划分的背景:
- 从计算过程来看,窄依赖,pipeline:流水线作业,速度快;宽依赖,跨节点数据传输,速度慢
窄依赖是数据以管道方式经一系列计算操作可以运行在一个集群节点上(如map,filter等)宽依赖则可能需要将数据通过跨节点传递运行(如groupByKey),有点类似于MapReduce的shuffle过程 - 窄依赖,容错快,宽依赖,容错慢
从失败恢复来看,窄依赖的失败恢复起来更高效,因为它只需找到父RDD的一个对应分区即可,而且可以在不同节点上并行计算做恢复;窄依赖则牵涉到父RDD的多个分区,恢复起来相对复杂。 - 综上,引入了一个新的概念Stage。一旦遇到宽依赖,需要切分stage。Stage可以简单理解为是由一组RDD组成的可进行优化的执行计划,如果RDD的衍生关系是窄依赖,则可以放在同一个stage中运行,若RDD的依赖关系是宽依赖,则要划分到不同的Stage。这样Spark在执行作业时,会按照stage的划分,生成一个完整的最优的执行计划
标准:
从父RDD到子RDD,父RDD的一个分区的数据,是给子RDD的一个分区使用,还是给到所有分区使用。
父RDD的一个分区的数据,给子RDD的一个分区使用,窄依赖
父RDD的一个分区的数据,给子RDD的所有分区使用,宽依赖
一旦RDD之间的依赖关系是宽依赖,就要切分stage
如果是宽依赖,就意味着数据要进行重新的分发,也就是要shuffle
依赖和容错
Dependency代表了RDD之间的依赖关系,血统。Lineage
窄依赖容错:
只要拿到父RDD的分区数据 + 当前RDD传入的函数,重新计算即可。速度快
宽依赖容错:
需要拿到父RDD所有分区的数据,重新计算,速度慢
如果某一个executor节点挂掉,driver会把在挂掉的executor上运行的task,重新的在其他的executor中,重新运行即可。
spark底层的容错机制:
缓存,持久化,写到内存,磁盘中
checkpoint,数据写入到分布式的文件系统中
DAG
每一个SparkJob都有一个DAG
DAG解析程序时,仅仅是记录了RDD之间的依赖关系,记录RDD中的操作函数
当触发action算子时,DAG就构建完成,job链解析完毕
spark任务运行机制
spark任务运行流程 分析
spark任务从spark-submit --master spark://hadoop000:7077 --class xx xx.jar input output开始运行
在spark-submit脚本中:
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"
会执行SparkSubmit这个类的main方法
该方法中,通过反射,调用自定义的main方法
SparkContext:spark任务的一个重要入口,会帮我们创建好所有后续程序运行需要的类,对象等
Driver端,会记录RDD间的依赖关系,调用方法传递的函数
调用action算子时,开始触发任务执行runjob
开始进行runjob,最终会调用DAScheduler的runJob,根据DAG有向无环图,进行stage切分
如何切分stage:根据finalRDD(调用action算子之前的那个RDD)创建一个finalStage:resultStage;深度优先遍历(从finalRDD到最原始RDD)
根据RDD的依赖关系,如果是窄依赖,就把父RDD添加到当前的stage中,如果是宽依赖,就把当前stage结束,启动一个新的stage;新的stage继续往前遍历,如果是窄依赖,添加到stage中,宽依赖就继续切分stage;直到某一个父RDD没有任何的依赖关系,切分stage结束。
stage之间,也有依赖观关系,真正运行的时候,需要一个stage的所有父stage全部执行完成之后,才能执行。
如何提交stage?从finalStage处开始提交,做了深度优先遍历,先提交所有的父stage,当父stage执行完毕之后,才能提交当前stage。
提交stage中的task? 根据stage的类型,来创建相应的task,用taskSet封装,然后把taskSet提交给TaskScheduler来进行调度。
task和stage一一对应
TaskScheduler开始调度task:每一个stage中有多少task,一般情况下,是由RDD的分区数量决定的。准确来说,是由每一个stage中最后一个RDD的分区数量决定的。
task是spark任务运行的最小单位,是一组分区的业务逻辑的综合;
taskSet:用于封装一个stage中的所有task类
TaskScheduler拿到taskSet,获取所有的task,然后把task进行序列化,把序列化之后的task发送给executor。
executor接收task任务:反序列化task,用TaskRunner把 task封装起来,TaskRunner将task封装起来,TaskRunner是一个线程,将封装好的taskRunner实例扔到线程池中等待被调度。最终根据task任务的类型,调用相应的runTask方法。当某一类Task的runTask方法被执行了,才是真正的并行执行任务。
spark运行机制
spark2.x 底层的通信机制:netty
spark1.6引入了netty,之前用的akka
引入netty的原因:
- akka在传输大数据块时,性能不佳,没有netty好
- akka要求使用者的版本和spark中的akka版本必须一致。
高级特性:缓存和持久化
Spark速度非常快的原因之一就是在不同操作中可以再内存中持久化或缓存数据集。当持久化某个RDD之后,每一个节点都将把计算的分片结果保存到内存中,并在对此RDD或衍生出的RDD进行其他动作中重用。这使得后续的动作变得更加迅速。
默认情况下,每个经过转换的RDD都会在它之上执行一个动作时被重新计算,导致同一个RDD会被从头计算多次。
因此,我们就需要把这些被调用多次的RDD进行持久化(写入到内存或写入磁盘等操作),以后使用时直接从内存读取,而不需要从头计算。
persist():
针对StorageLevel.MEMORY_ONLY提供了方法:cache
根据不同的参数,StorageLevel,共12种存储策略
StorageLevel.MEMORY_ONLY 仅内存
StorageLevel.MEMORY_AND_DISK 内存+磁盘
如何使用:
val formatData: RDD[(String, Double, Int)] = data.map(t => {
..........
persist(StorageLevel.MEMORY_ONLY)
cache(),persist()都是lazy执行的,当触发action之后才会把真正的数据写入到相应的存储介质
根据需要选择的不同的存储介质,调度时直接rdd1.cache()或rdd1.persist(StorageLevel.MEMORY_AND_DISK)
cache需要注意事项:可能会因为内存不足,导致了只cache到部分分区数据,或一个分区数据都没有被cache到
调用rdd1.unpersist()来清除存储数据
到分区数据被写入到内存或磁盘之后,以后对该RDD的操作,数据首先从内存或磁盘中加载,若没有,再根据RDD的依赖关系,从头开始加载
调用持久化和cache时,RDD的依赖是没有发生改变的
当一个RDD被调用2次以上,就可以考虑将其持久化
checkpoint(用的少)
针对于计算的链非常长,迭代的次数非常多,数据重新计算,非常消耗性能。
针对这些数据,可以使用checkpoint,将RDD中的数据写入到分布式系统中,这样就不用担心缓存在内存中丢失。
scala> val rdd = sc.textFile("hdfs://hadoop000:8020/wordcount/input/")
rdd: org.apache.spark.rdd.RDD[String] = hdfs://hadoop000:8020/wordcount/input/ MapPartitionsRDD[1] at textFile at <console>:24
sc.setCheckpointDir("hdfs://hadoop000:8020/ck-2019/")
scala> rdd.checkpoint
org.apache.spark.SparkException: Checkpoint directory has not been set in the SparkContext
总结:
- 依然是lazy执行,当触发action时,开始执行,会产生两个job,第一个job执行业务逻辑,第二个job把数据写入hdfs
- 必须在SparkContext上设置checkpointDir,不同的application可以使用同一个checkpointDir.
- 当某一个RDD被checkpoint之后,父依赖关系被删除,生成一个新的checkpointRDD,以后基于该RDD的所有操作,都是从这个checkpoint目录读取数据
大数据处理流程:
根据IP地址求归属地–写入mysql
需求分析:
ipaccess.log 日志文件
ip.txt 也叫:ip地址的规则数据 规则库,知识库,中间库等
规则库数据,是一直要维护的数据
该数据的特点:稳定,数据量不是很大,在大数据处理时常用
object MatchAndMySQL {
//定义一个方法:将ip地址转为10进制
def ip2Long(ip:String):Long={
val fragments: Array[String] = ip.split("[.]")
var ipNum = 0L
for(i <- 0 until fragments.length){
ipNum = fragments(i).toLong | ipNum << 8L
}
ipNum
}
//二分法搜索
def binarySearch(longIp: Long,ipRules :Array[(Long,Long,String)]):(String,Int) = {
//定义起始两个指针
var low = 0
var high = ipRules.length-1
while (low <= high){
//中间索引
val middle = (low + high) / 2
//获取中间索引值
val (start,end,province) = ipRules(middle)
if(longIp >= start && longIp <=end){
//找到
return (province,1)
}else if(longIp <= start){//左区间
high = middle - 1
}else{//右区间
low = middle + 1
}
}
//没有匹配到,默认返回值
("unknown",1)
}
def main(args: Array[String]): Unit = {
val sc = MySparkUtil(this.getClass.getSimpleName)
//数据格式:20090121000132581311000|115.20.36.118|tj.tt98.com|/tj.htm|Mozilla/4.0 (compatible; MSIE 6.0: Windows NT 5.1; SV1; TheWorld) |
val log: RDD[String] = sc.textFile("D:\\ipaccess.log")
//数据格式:1.0.1.0|1.0.3.255|16777472|16778239|亚洲|中国|福建|福州||电信|350100|China|CN|119.306239|26.075302
val ip: RDD[String] = sc.textFile("D:\\ip.txt")
//数据预处理 提取ip并转为10进制
val longIps: RDD[Long] = log.map(str => {
val split: Array[String] = str.split("\\|")
val strip = split(1)
//ip地址转为10进制
ip2Long(strip)
})
//数据预处理 提取起始ip和结尾ip
val ipRuleRdd: RDD[(Long, Long, String)] = ip.map(str => {
val split: Array[String] = str.split("\\|")
//起始ip和终止ip 省份
val start: Long = split(2).toLong
val end: Long = split(3).toLong
val province: String = split(6)
(start, end, province)
})
//两个RDD不能嵌套:map中的函数,是在executor中被调用的, filter是在driver端调用
//将两个10进制ip匹配
// longIps.map(longIp => {
// ipRuleRdd
// })
//解决RDD不能被嵌套:将RDD转为本地集合 (因为数据量不是很大 可用collect)
val ipRuleRdds: Array[(Long, Long, String)] = ipRuleRdd.collect()
//使用传统的for循环可解决问题,但效率低下
//ipRules 规则数据,有序,可使用二分法查找
val result: RDD[(String, Int)] = longIps.map(binarySearch(_,ipRuleRdds)).reduceByKey(_+_)
result.foreach(println)
//用jdbc写入到MySQL
result.foreachPartition(it=>{
var connection: Connection = null
var pstm1:PreparedStatement =null
//foreachPartition 在executor执行,不能被driver端捕获
try {
//DriverManager未实现序列化接口,只能写在foreachPartition内,满足闭包引用执行task
connection = DriverManager.getConnection("xxx", "", "")
pstm1 = connection.prepareStatement("crate table if not exists ip_access(province varchar(20),cnts int)")
pstm1.execute()
it.foreach(tp => {
val pstm2: PreparedStatement = connection.prepareStatement("insert into ip_access values (?,?)")
pstm2.setString(1, tp._1)
pstm2.setInt(2, tp._2)
pstm2.execute()
})
} catch {
case e => Exception
}finally {
if(null != pstm1) pstm1.close()
if(null != connection) connection.close()
}
})
sc.stop()
}
}
将connect写在foreachPartition之外,会有Task not serializable异常。
这是因为:DriverManager没有实现序列化。
闭包引用;在函数中引用来了一个外部变量(闭包引用),这个变量会和task一起序列化,发送给executor执行
但是这个类没有实现序列化接口,不能被序列化
try-catch注意事项:
在spark算子里的错误,不能在driver端捕获,算子的业务逻辑是在executor中执行的,不能在driver端捕获executor的错误
广播变量
目前广播变量的实现,使用的是TorrentBroadCast
广播变量的使用
RDD不能被广播
1- sc.broadcast(ipRuleRdds)
2- ipBc.value
//解决RDD不能被嵌套:将RDD转为本地集合 (因为数据量不是很大 可用collect)
val ipRuleRdds: Array[(Long, Long, String)] = ipRuleRdd.collect()
//将规则数据进行广播 ,RDD不能被广播
val ipBc: Broadcast[Array[(Long, Long, String)]] = sc.broadcast(ipRuleRdds)
//使用传统的for循环可解决问题,但效率低下
//ipRules 规则数据,有序,可使用二分法查找
val result: RDD[(String, Int)] = longIps.map(binarySearch(_,ipBc.value)).reduceByKey(_+_)
打jar包上传集群注意事项
改参数,local模式的参数要去掉
改ip地址localhost改为具体ip
当前机器防火墙需关闭
将外部jar依赖(如MySQL)放在spark的安装目录下的jars,或通过- -jars引入jar包
spark的共享变量
目前,spark中提供了两种类型的共享变量
- 广播变量
一处广播,处处使用 - 累加器
全局的累加器
object AccDemo {
def main(args: Array[String]): Unit = {
val sc = MySparkUtil(this.getClass.getSimpleName)
//计数器
var i = 0
//累加器
val acc: LongAccumulator = sc.longAccumulator("acc")
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6,7,9),2)
rdd.foreach(t => {
i+=1
acc.add(1)
})
println(rdd.count())
//结果是0,因为i是java变量,在driver端,rdd.foreach是在executor端,所以为0
println(i)
println(acc.value)
sc.stop()
}
}
注意项:
累加器的值,只能做加法运算
累加器全局有效
累加器在driver端定义,在executor端赋值
如果在driver端看结果,用acc.value,在executor端看结果,能看到累加器的name
计算程序运行时间:
//读取object的时间
val acc: LongAccumulator = sc.longAccumulator("readObjectTime")
val t1: Long = System.currentTimeMillis()
val obj = out.readObject()
val t2: Long = System.currentTimeMillis()
acc.add(t2 - t1)
println(acc.value)
URL匹配案例
文件url.db 文件数据格式为:MD5(url不含http://)+type
文件40690.txt,文件中数据格式为 url(MD5加密的)+type
需求:40096文件中的url地址经过加密后,如果跟url.db文件中的相同,则将url.db中的type更新为40096中的type,不同不做修改
/**
* 文件url.db 文件数据格式为:MD5(url不含http://)+type
* 文件40690.txt,文件中数据格式为 url(MD5加密的)+type
* 需求:40096文件中的url地址经过加密后,如果跟url.db文件中的相同,则将url.db中的type更新为40096中的type,不同不做修改
*/
object SparkURLDemo {
def main(args: Array[String]): Unit = {
val sc = MySparkUtil(this.getClass.getSimpleName)
//数据格式:http://007sn.com # 20
val orgData: RDD[String] = sc.textFile("D:\\40096.txt")
//数据格式:
val url: RDD[String] = sc.textFile("D:\\url.db1000")
//数据预处理
val orgProcess: RDD[(String, String)] = orgData.map(t => {
val split: Array[String] = t.split("\t#\t")
//获取url 去除http://前缀
val host: String = new URL(split(0)).getHost
//加密
val md5Url: String = UrlUtils.md5Encoding(host)
val types = split(1)
//因为类型长度问题,类型<10,需补全两位类型
val newTypes: String = types.length match {
case 1 => "0" + types
case 2 => types
}
(md5Url, newTypes)
})
val urlProcess: RDD[(String, String)] = url.map(str => {
val md5Url: String = str.substring(0, 14)
val types: String = str.substring(14)
(md5Url, types)
})
// key 左值 右值
val join: RDD[(String, (String, Option[String]))] = urlProcess.leftOuterJoin(orgProcess)
//方案1 :map实现
join.map{
case (md5Url, (urlTypes,orgTypes)) => {
//最简单的判断,如果orgTypes有值,为orgTypes orgTypes无值,返回urlTypes
orgTypes.getOrElse("old"+urlTypes)
//方式2:
/*orgTypes match {
case Some(v) => v
case None => urlTypes
}*/
}
}//.foreach(println)
//方案2 广播变量实现
val bc: Broadcast[collection.Map[String, String]] = sc.broadcast(orgProcess.collectAsMap())
urlProcess.map(str => {
//原始数据中的 key value
val value: collection.Map[String, String] = bc.value
(str._1,value.getOrElse(str._1,str._2))
})
.foreach(println)
sc.stop()
}
}
SparkOnYarn
配置:
cd /hadoop/hadoop-2.9.0/etc/hadoop
vim capacity-scheduler.xml
将原来的defaultXXX改为DominantResourceCalculator
修改所有yarn节点上的yarn-site.xml,在该文件中添加配置:
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
</property>
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>
如果不配置,在spark-on-yarn的client模式下,如果内存不足,会因为OOM报错
修改spark-env.sh:
cd /spark/spark-2.4.4-bin-hadoop2.7/conf
vim spark-env.sh
添加配置:
export YARN_CONF_DIR=/hadoop/hadoop-2.9.0/etc/hadoop/
否则报错:
Exception in thread “main” org.apache.spark.SparkException: When running with master ‘yarn’ either HADOOP_CONF_DIR or YARN_CONF_DIR must be set in the environment.
提交任务
有两种提交模式:
client - 客户端模式 - Driver运行在客户端
cluster - 集群模式 - Driver运行在集群
spark-submit --master yarn - - deploy-mode client - -class xx xx.jar input output
spark-submit --master yarn - - deploy-mode cluster - -class xx xx.jar input output
测试:
[root@hadoop000 bin]# ./spark-submit --master yarn --deploy-mode client --class org.apache.spark.examples.SparkPi /spark/spark-2.4.4-bin-hadoop2.7/examples/jars/spark-examples_2.11-2.4.4.jar 100
在cluster模式下,输出的内容在任务运行所在的节点上
spark-shell只能作用在 yarn client模式下
spark-on-yarn运行机制
yarn中的容器的资源配置
默认情况下,yarn中一个容器的最小资源是1024M,且默认分配规则是最小资源的整数倍
./spark-submit --executor-cores 2 --executor-memory 512m --conf spark.yarn.am.memory=1024m --master yarn --deploy-mode client --class org.apache.spark.examples.SparkPi /spark/spark-2.4.4-bin-hadoop2.7/examples/jars/spark-examples_2.11-2.4.4.jar 100
cluster模式下:ApplicationMaster 用的是Driver的配置
client模式下: ApplicationMaster 用的是指定的am.xxx的配置
spark on yarn的进程:
SparkSubmit:任务提交命令
CoarseGrainedExecutorBackend:executor运行
ApplicationMaster:Driver运行
杀死yarn的任务
[root@hadoop000 bin]# yarn application -list
yarn application -kill appID
Spark HA
指的只是在standalone模式下的高可用
在spark-env.sh中添加如下配置:
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER - Dspark.deploy.zookeeper.url=hdp-node-01:2181,hdp-node-02:2181,hdp-node-03:2181 -Dspark.deploy.zookeeper.dir=/spark"
将SPARK_MASTER_HOST注释掉
参数说明:
1.spark.deploy.recoveryMode:恢复模式(Master 重新启动的模式):有三种:(1):zookeeper(2):FileSystem(3):none
2.spark.deploy.zookeeper.url:zookeeper的server地址
3.spark.deploy.zookeeper.dir:保存集群元数据信息的文件,目录。包括Worker,Driver和Application。
注意:
在普通模式下启动spark集群,只需要在主机上面执行start-all.sh就可以了。
在高可用模式下启动spark集群,现需要在任意一台节点上启动start-all,然后在另外一台节点上单独启动master。命令:start-master.sh
主master挂掉之后,热备节点就会启动,一般情况,状态切换会有延迟,30s时间。
主备切换之间,提交到master的任务处于等待状态,等待新的master接管工作之后,才能正常调度执行。
当前HA模式下任务的提交方式:
spark-submit - -master spark://hadoop000:7077,hadoop001:7077