spark笔记
1、hadoop与spark的关系与区别
1.1、处理流程比较
总结:
1、Spark把运算的中间数据存放在内存,迭代计算效率更高;mapreduce的中间结果需要落地,需要保存到磁盘,这样必然会有磁盘io操做,影响性能。(不能说mr迭代不使用内存,只是不主打内存)
2、Spark容错性高,它通过弹性分布式数据集RDD来实现高效容错;mapreduce的话容错可能只能重新计算了,成本较高。
3、Spark更加通用,spark提供了transformation和action这两大类的多个功能api,另外还有流式处理sparkstreaming模块、图计算GraphX等等;mapreduce只提供了map和reduce两种操作,流计算以及其他模块的支持比较缺乏
2、spark部署模式
1.Local 多用于本地测试,如在eclipse,idea中写程序测试等。
单进程模拟多个线程(Master Worker Driver)
2.Standalone 是Spark自带的一个资源调度框架,它支持完全分布式。
Hadoop MapReduceV1 JobTracker+TaskTracker
Standalone Master+Worker
3.Yarn 生态圈里面的一个资源调度框架,Spark也是可以基于Yarn来计算的。
Hadoop MapReduceV2 on Yarn
Spark on YARN (推荐)
4.Mesos 资源调度框架,与Yarn类似。
Mesos和Yarn类似,官方推荐Spark on Mesos http://mesos.apache.org
版本:Spark 2.2(演示) +2.4.5(项目) 版本2.x
https://archive.apache.org/dist/spark/spark-2.2.0/
https://archive.apache.org/dist/spark/spark-2.4.5/
2.1、Standalone模式
2.1.1、单机搭建
#1、上传到/opt/softwares/中
#2、解压到/usr/local中
tar -zxvf spark-2.4.5-bin-hadoop2.7.tgz -C /usr/local/
#3、设置软链接,方便切换spark版本
ln -s ./spark-2.4.5-bin-hadoop2.7/ ./spark
#4、修改conf目录下的env文件
mv spark-env.sh.template spark-env.sh
vi spark-env.sh
在文件的末尾添加
export JAVA_HOME=/usr/local/jdk1.8 JDK安装路径
#5、修改slaves.template文件添加从节点
mv slaves.template slaves
vi slaves
内容(改成自己的,因为是单机):
master
配置Job History Server
1.启动HDFS
start-dfs.sh
创建directory目录
hdfs dfs -mkdir /directory
2.进入到spark安装目录conf目录下
cd /usr/local/spark/spark/conf
3.将spark-default.conf.template复制为spark-default.conf
cp spark-defaults.conf.template spark-defaults.conf
vi spark-defaults.conf在文件的末尾添加
spark.eventLog.enabled true 开启日志
spark.eventLog.dir hdfs://qianfeng01:8020/directory 存储路径
spark.eventLog.compress true 是否压缩
参数描述:
spark.eventLog.dir:Application在运行过程中所有的信息均记录在该属性指定的路径下
spark.eventLog.compress 这个参数设置history-server产生的日志文件是否使用压缩,true为使用,false为不使用。这个参数务可以成压缩哦,不然日志文件岁时间积累会过
4.修改spark-env.sh文件,添加如下配置
# Hadoop配置目录
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export SPARK_HISTORY_OPTS="-Dspark.history.ui.port=4000 -Dspark.history.retainedApplications=10 -Dspark.history.fs.logDirectory=hdfs://master:9000/spark-history"
spark.history.ui.port=4000 调整WEBUI访问的端口号为4000
spark.history.fs.logDirectory=hdfs://hadoop01:8020/directory 配置了该属性后,在start-history-server.sh时就无需再显式的指定路径,Spark History Server页面只展示该指定路径下的信息
spark.history.retainedApplications=10 指定保存Application历史记录的个数,如果超过这个值,旧的应用程序信息将被删除,这个是内存中的应用数,而不是页面上显示的应用数。
5.配置完成后分发文件到相应节点
scp -r ./spark-env.sh root@hadoop02:$PWD
scp -r ./spark-defaults.conf root@hadoop02:$PWD
ps:最好不要是用IE内核的浏览器不然效果是显示不出来的
启动的时候是
start-all.sh start-history-server.sh
其实直接启动就好了(最好启动hdfs后再启动)
[root@qianfeng01 sbin]# ./start-all.sh
[root@qianfeng01 sbin]# jps
60929 Jps
60854 Worker
60746 Mster
验证:
1、端口
[root@qianfeng01 sbin]# netstat -an|grep 7077
tcp6 0 0 192.168.1.101:7077 :::* LISTEN
tcp6 0 0 192.168.1.101:7077 192.168.1.101:44164 ESTABLISHED
tcp6 0 0 192.168.1.101:44164 192.168.1.101:7077 ESTABLISHED
[root@qianfeng01 sbin]# netstat -an|grep 8080
tcp6 0 0 :::8080 :::* LISTEN
tcp6 0 0 192.168.1.101:8080 192.168.1.1:21004 ESTABLISHED
tcp6 0 0 192.168.1.101:8080 192.168.1.1:21002 ESTABLISHED
tcp6 0 0 192.168.1.101:8080 192.168.1.1:21005 ESTABLISHED
tcp6 0 0 192.168.1.101:8080 192.168.1.1:21001 ESTABLISHED
tcp6 0 0 192.168.1.101:8080 192.168.1.1:21006 ESTABLISHED
tcp6 0 0 192.168.1.101:8080 192.168.1.1:21003 ESTABLISHED
2.webui
http://192.168.1.200:8080
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vUJE3Dk-1630938615676)(C:\Users\等待\AppData\Roaming\Typora\typora-user-images\image-20210609210417566.png)]
测试,运行实例程序
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://qianfeng01:7077 \
$SPARK_HOME/examples/jars/spark-examples_2.11-2.2.0.jar \
100
2.1.2、集群搭建
即修改下配置文件
基本条件:同步时间、免密登录、关闭防火墙、安装JDK1.8
1.上传安装包到qianfeng01
2.将文件解压到指定的目录
tar -zxvf spark-2.2.0-bin-hadoop2.7.tgz -C /usr/local/
3.跳转到安装路径进入到conf进行配置
cd /opt/software/spark-2.2.0-bin-hadoop2.7/
cd conf/
3.1修改conf目录下的env文件
mv spark-env.sh.template spark-env.sh
vi spark-env.sh
在文件的末尾添加
export JAVA_HOME=/usr/local/jdk1.8 JDK安装路径
3.2修改slaves.template文件添加从节点
mv slaves.template slaves
vi slaves
内容(根据自己的节点适当删减):
qianfeng02
qianfeng03
qianfeng04
4.分发配置好的内容到其他节点:
scp -r ./spark-2.2.0-bin-hadoop2.7/ root@hadoop04:$PWD
ps:0后面进行修改 2,3,4
配全局环境变量(选配好处:可以在任意位置使用bin下脚本,如spark-shell和sparkSubmit):
vi /etc/profile
export SPARK_HOME=/opt/software/spark-2.2.0-bin-hadoop2.7
需要在引用路径的最后添加 $SPARK_HOME/bin:
保存退出
source /etc/profile
spark启动集群:
进入到安装目录找sbin目录进入 /opt/software/spark-2.2.0-bin-hadoop2.7
启动 ./start-all.sh
spark提供webUI界面端和tomcat的端口是一样8080 内部通信7077
http://qianfeng01:8080
配置Job History Server
1.启动HDFS
start-dfs.sh
创建directory目录
hdfs dfs -mkdir /directory
2.进入到spark安装目录conf目录下
cd /opt/software/spark-2.2.0-bin-hadoop2.7/conf
3.将spark-default.conf.template复制为spark-default.conf
mv spark-defaults.conf.template spark-defaults.conf
vi spark-defaults.conf在文件的末尾添加
spark.eventLog.enabled true 开启日志
spark.eventLog.dir hdfs://qianfeng01:8020/directory 存储路径
spark.eventLog.compress true 是否压缩
参数描述:
spark.eventLog.dir:Application在运行过程中所有的信息均记录在该属性指定的路径下
spark.eventLog.compress 这个参数设置history-server产生的日志文件是否使用压缩,true为使用,false为不使用。这个参数务可以成压缩哦,不然日志文件岁时间积累会过
4.修改spark-env.sh文件,添加如下配置
export SPARK_HISTORY_OPTS="-Dspark.history.ui.port=4000 -Dspark.history.retainedApplications=10 -Dspark.history.fs.logDirectory=hdfs://qianfeng01:8020/directory"
spark.history.ui.port=4000 调整WEBUI访问的端口号为4000
spark.history.fs.logDirectory=hdfs://hadoop01:8020/directory 配置了该属性后,在start-history-server.sh时就无需再显式的指定路径,Spark History Server页面只展示该指定路径下的信息
spark.history.retainedApplications=10 指定保存Application历史记录的个数,如果超过这个值,旧的应用程序信息将被删除,这个是内存中的应用数,而不是页面上显示的应用数。
5.配置完成后分发文件到相应节点
scp -r ./spark-env.sh root@hadoop02:$PWD
scp -r ./spark-defaults.conf root@hadoop02:$PWD
ps:最好不要是用IE内核的浏览器不然效果是显示不出来的
启动的时候是
start-all.sh start-history-server.sh
得到分区的目前的三种方式
1、inputrdd.glom.collect
这个直观一些
2、inputrdd.partitions.size
3、inputrdd.getNumPartitions
算子中的函数是在executor端,其他的在driver端
coalesce
多的分区向少的分区转换可以不用shuffle
但是少的分区向多的分区转换必须用shuffle,如果不用的话分区不变
3、spark-shell
路径在:$SPARK_HOME/bin/spark-shell
支持四种不同的部署模式
local下的示例
$SPARK_HOME/bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master local[2] \
--num-executors 1 \
$SPARK_HOME/examples/jars/spark-examples_2.11-2.2.0.jar \
100
提示:local[2] 代表分配两个cpu,默认是local[*]
spark-shell启动
spark-shell --master local[*]
yarn下的spark-shell启动
spark-shell --master yarn
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0X83c2Q-1630938615680)(C:\Users\等待\AppData\Roaming\Typora\typora-user-images\image-20210610191229420.png)]
5、RDD算子
提示:创建一个RDD就会生成RDD依赖关系。
5.1、Transform算子
规律:
只要返回的是RDD的函数,都是Transform算子。
特点:
对RDD仅进行transform操作,并不会立即执行,只有遇到Action才会触发执行(类似于延迟加载lazy)。
如何创建一个RDD?
sc.textFile()
sc.makeRDD(Seq)
sc.parallelize(1 to 10)
scala> sc.makeRDD
def makeRDD[T](seq: Seq[(T, Seq[String])])(implicit evidence$3: scala.reflect.ClassTag[T]): org.apache.spark.rdd.RDD[T]
def makeRDD[T](seq: Seq[T],numSlices: Int)(implicit evidence$2: scala.reflect.ClassTag[T]): org.apache.spark.rdd.RDD[T]
scala> sc.parallelize
def parallelize[T](seq: Seq[T],numSlices: Int)(implicit evidence$1: scala.reflect.ClassTag[T]): org.apache.spark.rdd.RDD[T]
scala> sc.parallelize(1 to 10)
res0: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:25
scala> sc.makeRDD(1 to 10)
res1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at makeRDD at <console>:25
PairRDD(下面算子都是)
包括kv对偶(二元组)的RDD称为PairRDD,所有的算子定义在org.apache.spark.rdd.PairRDDFunctions,KV对RDD可以隐式转换为PairRDD
PairRDDFunctions包括所有对KV RDD的处理操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NqEG9LBW-1630938615682)(file://G:\BigData2021资料\spark\day02\2.笔记\2101Spark课堂笔记.assets\image-20210610105057347.png?lastModify=1623334969)]
提示:凡是带bykey的基本都是PairRDD,例外是mapValues,这个也是
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RHkYLUtO-1630938615685)(file://G:\BigData2021资料\spark\day02\2.笔记\2101Spark课堂笔记.assets\image-20210610105232486.png?lastModify=1623334983)]
常见简单算子介绍:
注意:在spark中,最基本的原则就是每个task处理一个RDD的partition。
5.1.1map、mapPartitions、mapPartitionsWithIndex
map:对数据进行的映射,将RDD中的每一个元素,都映射为参数函数返回的结果,并返回一个新的RDD
mapPartitions: 是map算子的变种, 也是对RDD中的数据进行的映射;
map与mapPartitions的区别:
1、 map算子是作用在每一个分区中的每一个元素上的, mapPartitions是作用在分区上的。
2、 mapPartitions会将一个分区,作为一个整体,来进行映射
mapPartitionsWithIndex:
类似于mapPartitions,也是对一个分区进行的整体的计算,但是比mapPartitions多出一个分区的下标
mapPartitions的优势:
注意:在spark中,最基本的原则就是每个task处理一个RDD的partition
首先要举个例子:对于普通的map来说,对于一个partition中有两万条数据,好家伙,那么function就要执行和计算两万次,但是如果使用了mapPartitions后,一个task仅仅会执行一次function,function一次接受所有的partition中的数据。只需要执行一次就足够了,相比较map来说,性能高。
但是缺点也是存在的,容易OOM(out of memory)
如果是普通的map操作,⼀次function的执⾏就处理⼀条数据;那么如果内存不够⽤的情况下,⽐如处理了1千条数据了,那么这个时候内存不够了,那么就可以将已经处理完的1千条数据从内存⾥⾯垃圾回收掉,或者⽤其他⽅法,腾出空间来吧。
mapPartition():每次处理⼀个分区的数据,这个分区的数据处理完后,原RDD中分区的数据才能释放,可能导致OOM。所以说普通的map操作通常不会导致内存的OOM异常。
在项⽬中,⾃⼰先去估算⼀下RDD的数据量,以及每个partition的量,还有⾃⼰分配给每个executor
的内存资源。看看⼀下⼦内存容纳所有的partition数据,⾏不⾏。如果⾏,可以试⼀下,能跑通就好。
性能肯定是有提升的。
5.1.2、flatMap
功能:FlatMap 算子和 Map 算子类似, 但是 FlatMap 是一对多
格式:
def flatMap[U: ClassTag](f: T ⇒ List[U]): RDD[U]
参数:f
→ 参数是原 RDD 数据, 返回值是经过函数转换的新 RDD 的数据, 需要注意的是返回值是一个集合, 集合中的数据会被展平后再放入新的 RDD
下面两个图示解释:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uuSI1vhc-1630938615688)(C:\Users\等待\AppData\Roaming\Typora\typora-user-images\1624699970288.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iAuVABaX-1630938615690)(C:\Users\等待\AppData\Roaming\Typora\typora-user-images\1624700234035.png)]
def flatMapTest():Unit={
//创建RDD
val rdd1 = sc.parallelize(Seq("Hello lilty","hello wen","lilty Hello"))
//处理数据
val rdd2 = rdd1.flatMap(item => item.split(" "))
//得到结果
rdd2.collect().foreach(item => println(item))
//关闭sc
sc.stop()
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wyZnSNhx-1630938615691)(C:\Users\等待\AppData\Roaming\Typora\typora-user-images\1624700184154.png)]
5.1.3、map与flatmap的区别
map:是一对一的关系,对数据进行的映射,将RDD中的每一个元素,都映射为参数函数返回的结果,并返回一个新的RDD
flatmap: 是一对多的关系,“先映射,后扁平化”,map对每一次(func)都产生一个元素,返回一个对象,而flatMap多一步就是将所有对象合并为一个对象。而flatten起作用的前提是:它之前的map操作使集合中装的元素变成了集合。因此,flatMap内部才经常搭配split使用,正是因为split操作后可以生成集合。
5.1.4、glom
功能: 将每一个分区形成一个数组,形成新的RDD类型时RDD[Array[T]]
def glomeTest(): Unit ={
// 将1-20的数据,分到4个分区中
val sourceRDD: RDD[Int] = sc.parallelize(1 to 20, 4)
// 将每一个分区的所有的数据,聚合到一个数组中
val res: RDD[Array[Int]] = sourceRDD.glom()
res.foreach(arr => println(arr.mkString(", "),"此数组引用为",arr))
println(res.getNumPartitions)
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vsS6Ds4-1630938615693)(C:\Users\等待\AppData\Roaming\Typora\typora-user-images\1624700857591.png)]
5.1.1、keyBy
格式:
def keyBy[K](f: T => K): RDD[(K, T)]
通过在每个元素上应用一个函数生成键值对中的键,最后构成一个键值对元组。
scala> val a = sc.parallelize(List("dog", "salmon", "salmon", "rat", "elephant"), 3)
a: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[76] at parallelize at <console>:24
scala> a.collect
res62: Array[String] = Array(dog, salmon, salmon, rat, elephant)
scala> val b = a.keyBy(_.length)
b: org.apache.spark.rdd.RDD[(Int, String)] = MapPartitionsRDD[77] at keyBy at <console>:26
scala> b.collect
res63: Array[(Int, String)] = Array((3,dog), (6,salmon), (6,salmon), (3,rat), (8,elephant))
5.1.2、groupBy(func)
任意普通RDD都有groupby
场景:分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。
scala> val rdd = sc.parallelize(1 to 20)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[66] at parallelize at <console>:24
scala> rdd.groupBy(_%2).collect
res54: Array[(Int, Iterable[Int])] = Array((0,CompactBuffer(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)), (1,CompactBuffer(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)))
5.1.3、groupByKey
PairRDD特有的
按照key值进行分组,生成了一组CompactBuffer结构的数据。
注意1:每次结果可能不同,因为发生shuffle
示例1:
scala> val words = Array("one", "two", "two", "three", "three", "three")
words: Array[String] = Array(one, two, two, three, three, three)
//先变成PairRDD
scala> val wordPairsRDD = sc.parallelize(words).map(word => (word, 1))
wordPairsRDD: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[62] at map at <console>:26
scala> wordPairsRDD.collect
res51: Array[(String, Int)] = Array((one,1), (two,1), (two,1), (three,1), (three,1), (three,1))
scala> wordPairsRDD.groupByKey().collect
res52: Array[(String, Iterable[Int])] = Array((two,CompactBuffer(1, 1)), (one,CompactBuffer(1)), (three,CompactBuffer(1, 1, 1)))
scala> wordPairsRDD.groupByKey().map(x=>(x._1,x._2.sum)).collect
res53: Array[(String, Int)] = Array((two,2), (one,1), (three,3))
示例2:
scala> val a = sc.parallelize(List("dog", "tiger", "lion", "cat", "spider", "eagle"), 2)
a: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[73] at parallelize at <console>:24
scala> a.collect
res58: Array[String] = Array(dog, tiger, lion, cat, spider, eagle)
scala> val b = a.keyBy
def keyBy[K](f: String => K): org.apache.spark.rdd.RDD[(K, String)]
scala> val b = a.keyBy(_.length)
b: org.apache.spark.rdd.RDD[(Int, String)] = MapPartitionsRDD[74] at keyBy at <console>:26
scala> b.collect
res60: Array[(Int, String)] = Array((3,dog), (5,tiger), (4,lion), (3,cat), (6,spider), (5,eagle))
scala> b.groupByKey.collect
res61: Array[(Int, Iterable[String])] = Array((4,CompactBuffer(lion)), (6,CompactBuffer(spider)), (3,CompactBuffer(dog, cat)), (5,CompactBuffer(tiger, eagle)))
5.1.4、reduceByKey
reduceByKey=groupByKey+reduce(示例1)
scala> rdd.map((_,1)).reduceByKey(_+_).collect
res73: Array[(String, Int)] = Array((two,2), (one,1), (three,3))
scala> rdd.map((_,1)).groupByKey.map(kv=>(kv._1,kv._2.reduce(_+_))).collect
res75: Array[(String, Int)] = Array((two,2), (one,1), (three,3))
注意:也进行 分区内聚合,不能说不能,与Aggretebykey 一样(先分区内聚合,在分区间聚合),本质是调用一个方法(忘了),看一下视频
解释:对元素为KV对的RDD中Key相同的元素的Value进行reduce操作
示例1:
scala> val words = Array("one", "two", "two", "three", "three", "three")
words: Array[String] = Array(one, two, two, three, three, three)
//转换成PairRDD
scala> val wordPairsRDD = sc.parallelize(words).map(word => (word, 1))
wordPairsRDD: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[100] at map at <console>:26
scala> wordPairsRDD.m