目录
一、Spark编程入门
1. 通过IDEA创建Spark工程
ps:工程创建之前步骤省略,在scala中已经讲解,直接默认是创建好工程的。
导入Pom文件依赖,将如下的内容覆盖pom.xml中的<properties>和<dependencies>标签,注意别把最后一行</project>标签覆盖掉。
<!-- 声明公有的属性 -->
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<encoding>UTF-8</encoding>
<scala.version>2.12.8</scala.version>
<spark.version>3.1.2</spark.version>
<hadoop.version>3.3.1</hadoop.version>
<scala.compat.version>2.12</scala.compat.version>
</properties>
<!-- 声明并引入公有的依赖 -->
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
</dependencies>
在Pom.xml文件中添加
<!-- 配置构建信息 -->
<build>
<!-- 资源文件夹 -->
<sourceDirectory>src/main/scala</sourceDirectory>
<!-- 声明并引入构建的插件 -->
<plugins>
<!-- 用于编译Scala代码到class -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<!-- 程序打包 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<!-- 过滤掉以下文件,不打包 :解决包重复引用导致的打包错误-->
<filters>
<filter><artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<!-- 打成可执行的jar包 的主方法入口-->
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass></mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
更新完pom.xml文件后,刷新一下下载依赖,等待依赖下载完成。
2. Scala实现WordCount
在scala package下新建com.spark_example package下编写第一个spark程序 new scala class命名为SparkCore_demo1
package com.spark_example
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
/**
* spark的第一个程序
*/
object SparkCore_demo1 {
def main(args: Array[String]): Unit = {
//1、获取spark的context对象,该对象是上下文--->local[*] *:使用电脑可用cpu核算
val sc = new SparkContext("local[*]", "spark-wc")
//2、使用sc对象初始化数据源
val rdd:RDD[String] = sc.textFile("C:\\Users\\80621\\Desktop\\words\\book1.txt")
//3、将第二步中的数据进行加工处理
val sumRdd = rdd.flatMap(_.split(" "))
.map(x => (x, 1))
.reduceByKey(_ + _)
//4、对第三步中的数据进行存储
println(sumRdd.collect().toBuffer)
sumRdd.saveAsTextFile("C:\\Users\\80621\\Desktop\\words\\out")
//3、4步骤可以写一个行
//rdd.flatMap(_.split("\t")).map(x => (x, 1)).reduceByKey(_ + _).saveAsTextFile("")
//5、关闭sc对象
sc.stop()
}
}
3. 程序打包上传集群
编写文件放到服务器上去跑,new scala class命名为SparkCore_demo2
package com.spark_example
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object SparkCore_demo2 {
def main(args: Array[String]): Unit = {
//1、获取spark的context对象,该对象是上下文
val sc = new SparkContext(new SparkConf().setAppName("spark-wc"))
//2、使用sc对象初始化数据源
val rdd:RDD[String] = sc.textFile(args(0))
//3、将第二步中的数据进行加工处理
val sumRdd = rdd.flatMap(_.split(" "))
.map(x => (x, 1))
.reduceByKey(_ + _)
//4、对第三步中的数据进行存储
println(sumRdd.collect().toBuffer)
sumRdd.saveAsTextFile(args(1))
//5、关闭sc对象
sc.stop()
}
}
首先clean
然后打包
打包好的文件在工程目录下的target目录下 C:\Users\80621\IdeaProjects\QD-Bigdata\target
将original-QD-Bigdata-1.0-SNAPSHOT.jar通过mobaxtermchs上传至softwares中
mv /root/softwares/original-QD-Bigdata-1.0-SNAPSHOT.jar /home/original-QD-Bigdata-1.0.jar
进行提交作业操作
spark-submit \ --class com.spark_example.SparkCore_demo2 \ --master yarn \ --deploy-mode client \ /home/original-QD-Bigdata-1.0.jar /input1 /output1/701
查看结果
hdfs dfs -cat /output1/701/*
二、算子介绍
2.1 RDD概念
- RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
- 在之前学习MR的过程中对数据是没有进行抽象的,而在Spark中对数据进行了抽象,提供一些列处理方法也就是说RDD(弹性分布式数据集),Spark计算的基石,为用户屏蔽了底层对数据的复杂抽象和处理,为用户提供了一组方便的数据转换与求值方法。
- 现在开发的过程中都是面向对象的思想,那么我们创建类的时候会对类封装一些属性和方法,那么创建出来的对象就具备着这些属性和方法,类也属于对数据的抽象,而Spark中的RDD就是对操作数据的一个抽象
- 查看原码可以得知,而且在类中提供了很多方法可以使用
- 弹性
a.存储的弹性:内存与磁盘的自动切换;
b.容错的弹性:数据丢失可以自动恢复;
c.计算的弹性:计算出错重试机制;
d.分片的弹性:可根据需要重新分片。
- 分布式:数据存储在大数据集群不同节点上
- 数据集:RDD封装了计算逻辑,并不保存数据
- 数据抽象:RDD是一个抽象类,需要子类具体实现
- 不可变:RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑
- 可分区、并行计算
总结:
在 Spark 中,对数据的所有操作不外乎创建 RDD、转化已有RDD 以及调用 RDD 操作进行求值。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含 Python、Java、Scala 中任意类型的对象, 甚至可以包含用户自定义的对象。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
2.2 RDD做了什么
从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。执行时,需要将计算资源和计算模型进行协调和整合。
Spark框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计算。最后得到计算结果。
RDD是Spark框架中用于数据处理的核心模型,例如在SparkShell,执行如下命令:
sc.textFile(“xx").flatMap(_.split("")).map((_,1)).reduceByKey(_+_).saveAsTextFile(“xx")
总结:
从以上流程可以看出RDD在整个流程中主要用于将逻辑进行封装,RDD的创建->RDD的转换(转换过程中为了减少数据计算有添加缓存)->RDD的行动(输出数据)
2.3 RDD五大特性
RDD源码中提供了说明
1)一组分片(Partition),即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目。
#RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。2)一个计算每个分区的函数。Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。
#Spark在计算时,是使用分区函数对每一个分区进行计算3)RDD之间的依赖关系。RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。
#RDD是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个RDD建立依赖关系
4)一个Partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。
#当数据为KV类型数据时,可以通过设定分区器自定义数据的分区5)一个列表,存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。
#计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
注意 : RDD本身是不存储数据,可以看做RDD本身是一个引用数据
2.4 RDD的弹性
1. 自动进行内存和磁盘数据存储的切换
Spark优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换
2. 基于血统的高效容错机制
在RDD进行转换和动作的时候,会形成RDD的Lineage依赖链,当某一个RDD失效的时候,可以通过重新计算上游的RDD来重新生成丢失的RDD数据。
3. Task如果失败会自动进行特定次数的重试
RDD的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是4次。
4. Stage如果失败会自动进行特定次数的重试
如果Job的某个Stage阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是4次。
5. Checkpoint和Persist可主动或被动触发
RDD可以通过Persist持久化将RDD缓存到内存或者磁盘,当再次用到该RDD时直接读取就行。也可以将RDD进行检查点,检查点会将数据存储在HDFS中,该RDD的所有父RDD依赖都会被移除。
6. 数据调度弹性
Spark把这个JOB执行模型抽象为通用的有向无环图DAG,可以将多Stage的任务串联或并行执行,调度引擎自动处理Stage的失败以及Task的失败。
总结:
存储的弹性:内存与磁盘的
自动切换容错的弹性:数据丢失可以
自动恢复计算的弹性:计算出错重试机制
分片的弹性:根据需要重新分片
2.5 创建RDD
在Spark中创建RDD的创建方式可以分为四种:
2.5.1 从集合(内存)中创建RDD
从集合中创建RDD,Spark主要提供了两个方法:parallelize和makeRDD
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
/**
* spark的创建
*/
object Demo03_RddCreate {
def main(args: Array[String]): Unit = {
//1、获取spark的context对象,该对象是上下文--->local[*] *:使用电脑可用cpu核算
val sc = new SparkContext("local[*]", "spark-wc")
//2.从集合(内存)中创建RDD --》 这种创建方式多用于测试使用
//makeRDD和parallelize是可以指定分区数量的,有第二个参数,默认值是2,也可以指定这个值
val rdd1: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6))
val rdd2: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6),3)
println(rdd1.collect().toBuffer)
println(rdd2.collect().toBuffer)
//3、关闭sc对象
sc.stop()
}
}
2.5.2 从外部存储(文件)创建RDD
由外部存储系统的数据集创建RDD包括:本地的文件系统,所有Hadoop支持的数据集,比如HDFS、HBase等
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CreateSparkRDDDemo {
def main(args: Array[String]): Unit = {
//先创建SparkConf和SparkContext对象
val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local")
val sc = new SparkContext(conf)
//2.从外部存(文件)创建RDD
val rdd3: RDD[String] = sc.textFile("hdfs://user1:9820/input")
}
}
2.5.3 从其他RDD创建
主要是通过一个RDD运算完后,再产生新的RDD
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CreateSparkRDDDemo {
def main(args: Array[String]): Unit = {
//先创建SparkConf和SparkContext对象
val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local")
val sc = new SparkContext(conf)
val rdd3: RDD[String] = sc.textFile("hdfs://user1:9820/word.txt")
//3.从其他的RDD创建
val rdd4: RDD[String] = rdd3.flatMap(_.split(" "))
}
}
2.5.6 直接创建RDD(new)
使用new的方式直接构造RDD,一般由Spark框架自身使用。
2.6 RDD分类
RDD的具体实现类有几十种(大概60+),介绍下最常见的几种:
源数据RDD:
spark支持读取不同的数据源,如下例子:
- 支持hdfs文件读取, HadoopRDD
- 支持jdbc读取数据库,JdbcRDD
MapPartitionsRDD
MapPartitionsRDD对于父RDD的依赖类型只能是OneToOneDependency,代表将函数应用到每一个分区的计算。
相关transformation:map, flatMap, filter, mapPartitions 等
ShuffledRDD
- 对于父RDD的依赖类型只能是ShuffleDependency,代表需要改变分区方式进行shuffle的计算。
- 会创建ShuffledRDD的transformation:reduceByKey, sortByKey 等
- 默认情况下,Spark可以将一个作业切分多个任务后,发送给Executor节点并行计算,而能够并行计算的任务数量我们称之为并行度。这个数量可以在构建RDD时指定。记住,这里的并行执行的任务数量,并不是指的切分任务的数量,不要混淆了。
2.7 Transformation算子概述
RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这种设计让Spark更加有效率地运行。
RDD支持两种操作:转换操作(Transformation)和行动操作(Action),RDD的转换操作是返回一个新的RDD的操作,比如map和 flatMap,而行动操作则是向Driver返回结果或将结果写出到外部存在设备,比如,collect和saveAsTextFile。
列举部分算子:
转换 | 含义 |
map(func) | 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成 |
filter(func) | 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) | 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素) |
mapPartitions(func) | 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U] |
mapPartitionsWithIndex(func) | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U] |
sample(withReplacement, fraction, seed) | 根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子 |
union(otherDataset) | 对源RDD和参数RDD求并集后返回一个新的RDD |
intersection(otherDataset) | 对源RDD和参数RDD求交集后返回一个新的RDD |
distinct([numTasks])) | 对源RDD进行去重后返回一个新的RDD |
groupByKey([numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD |
reduceByKey(func, [numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置 |
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | 相同的Key值进行聚合操作,在聚合过程中同样使用了一个中立的初始值zeroValue:中立值,定义返回value的类型,并参与运算seqOp:用来在同一个partition中合并值combOp:用来在不同partiton中合并值 |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
sortBy(func,[ascending], [numTasks]) | 与sortByKey类似,但是更灵活 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
cartesian(otherDataset) | 笛卡尔积 |
pipe(command, [envVars]) | 将一些shell命令用于Spark中生成新的RDD |
coalesce(numPartitions) | 重新分区 |
repartition(numPartitions) | 重新分区 |
repartitionAndSortWithinPartitions(partitioner) | 重新分区和排序 |
2.8 Action算子概述
在RDD上运行计算,并返回结果给Driver或写入文件系统
动作 | 含义 |
reduce(func) | 通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的 |
collect() | 在驱动程序中,以数组的形式返回数据集的所有元素 |
count() | 返回RDD的元素个数 |
first() | 返回RDD的第一个元素(类似于take(1)) |
take(n) | 返回一个由数据集的前n个元素组成的数组 |
takeSample(withReplacement,num, [seed]) | 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子 |
takeOrdered(n, [ordering]) | takeOrdered和top类似,只不过以和top相反的顺序返回元素 |
saveAsTextFile(path) | 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 |
saveAsSequenceFile(path) | 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。 |
saveAsObjectFile(path) | |
countByKey() | 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。 |
foreach(func) | 在数据集的每一个元素上,运行函数func进行更新。 |
算子实战练习
C:\Users\80621\Desktop\words\stu.txt内容如下
12 张三 25 男 chinese 50
12 张三 25 男 math 60
12 张三 25 男 english 70
12 李四 20 男 chinese 50
12 李四 20 男 math 50
12 李四 20 男 english 50
12 王芳 19 女 chinese 70
12 王芳 19 女 math 70
12 王芳 19 女 english 70
13 张大三 25 男 chinese 60
13 张大三 25 男 math 60
13 张大三 25 男 english 70
13 李大四 20 男 chinese 50
13 李大四 20 男 math 60
13 李大四 20 男 english 50
13 王小芳 19 女 chinese 70
13 王小芳 19 女 math 80
13 王小芳 19 女 english 70
需求如下:
1. 一共有多少人参加考试?
一共有多少个女生参加考试?
2. 12班有多少人参加考试?
13班有多少人参加考试?
3. 语文科目的平均成绩是多少?
数学科目的平均成绩是多少?
英语科目的平均成绩是多少?
4. 单个人平均成绩是多少?
5. 12班平均成绩是多少?
12班男生平均总成绩是多少?
12班女生平均总成绩是多少?
同理求13班相关成绩
6. 全校语文成绩最高分是多少?
12班语文成绩最低分是多少?
13班数学最高成绩是多少?
问题1代码如下:
package com.spark_example
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
case class Stu(classID:String,name:String,age:Int,sex:String,subject:String,score:Double)
object StuCount {
def main(args: Array[String]): Unit = {
//获取spark的context对象,该对象是上下文--->local[*] *:使用电脑可用cpu核算
val sc = new SparkContext("local[*]", "spark-wc")
val rdd:RDD[String] = sc.textFile("C:\\Users\\80621\\Desktop\\words\\stu.txt")
val stuRdd = rdd
.filter(_.length > 0) //过滤掉空行
.map(line =>{
val fields = line.split(" ")
Stu(
fields(0).trim,
fields(1).trim,
fields(2).trim.toInt,
fields(3).trim,
fields(4).trim,
fields(5).trim.toDouble
)
})
//1、一共有多少人参加考试
val totalStu = stuRdd.map(x => x.name).distinct().count()
println("一共有:"+totalStu+" 人参加考试")
//2、一共有多少女生参加考试
val totalGirlStu = stuRdd.filter(x=>x.sex.equals("女")).map(x => x.name).distinct().count()
println("一共有:"+totalGirlStu+" 个女生参加考试")
//关闭sc对象
sc.stop()
}
}