spark笔记

这篇博客详细介绍了Spark与Hadoop的区别、Spark的部署模式、RDD的创建与算子,特别是Transform算子的使用,包括map、flatMap、mapPartitions等。还深入讲解了RDD的依赖关系、宽依赖与窄依赖的概念以及shuffle原理,强调了DAG、Stage、Task的角色。此外,博客还提到了广播变量和累加器的应用,以及shuffle过程中ShuffleWriter和ShuffleReader的工作流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值