这里写目录标题
Spark简介
Spark 是加州大学伯克利分校 AMP(Algorithms,Machines,People)实验室开发的通用内存并行计算框架。
发展历程
- 2009年诞生于加州大学伯克利分校AMP实验室
- 2010年正式开源
- 2013年6月正式成为Apache孵化项目
- 2014年2月成为Apache顶级项目
- 2014年5月正式发布Spark 1.0版本
- 2014年10月Spark打破MapReduce保持的排序记录
- 2015年发布了1.3、1.4、1.5版本
- 2016年发布了1.6、2.x版本
为什么使用Spark
- MapReduce编程模型的局限性
①繁杂:虽然操作只有map和reduce,但是复杂的逻辑需要大量的样例代码
②处理效率低:map中间结果要写入磁盘,reduce结果要写入hdfs,多个map要通过hdfs交换数据;任务调度与启动的开销大
③不适合迭代处理、交互式处理和流式处理 - Spark是类Hadoop MapReduce的通用并行框架
①spark中job的中间输出结果可以保存在内存,不需要读写hdfs
②比MapReduce平均快10倍以上
Spark优势
-
速度快
①基于内存数据处理,比mapreduce快100个数量级以上
②基于硬盘数据处理,比mapreduce快10个数量级以上 -
易用性
①支持Java、Scala、Python、R语言
②交互式shell方便开发 -
通用性
一栈式解决方案:批处理、交互式查询、实时流处理、图计算及机器学习 -
随处运行
支持多种运行模式:YARN、Local、Standalone、Mesos、EC2、Kubernetes
Spark技术栈
-
Spark Core
核心组件,分布式计算引擎 -
Spark SQL
高性能的基于Hadoop的SQL解决方案 -
Spark Streaming
可以实现高吞吐量、具备容错机制的准实时流处理系统 -
Spark GraphX
分布式图处理框架 -
Spark MLlib
构建在Spark上的分布式机器学习库
Spark常用三种运行模式
-
Local模式
Local 模式是最简单的一种Spark运行方式,它采用单节点多线程)方式运行,local模式是一种OOTB(开箱即用)的方式,只需要在spark-env.sh配置JAVA_HOME,无需其他任何配置即可使用,常用于开发和学习
方式:spark-shell - -master local[n]
,其中n代表线程数(核数) -
Standalone模式
Spark可以通过部署与Yarn的架构类似的框架来提供自己的集群模式,该集群模式的架构设计与HDFS和Yarn相似,都是由一个主节点多个从节点组成,在Spark 的Standalone模式中,主为:master,从为:worker
// spark-env.sh中配置如下
SPARK_MASTER_HOST=192.168.233.133 //配置Master节点
SPARK_WORKER_CORES=2 //配置应用程序允许使用的核数(默认是所有的core)
SPARK_WORKER_MEMORY=2g //配置应用程序允许使用的内存(默认是一个G)
// slaves配置如下,添加所有worker的ip地址
ip地址1
ip地址2
ip地址n
- Spark on Yarn
Spark on Yarn 模式就是将Spark应用程序跑在Yarn集群之上,通过Yarn资源调度将Executor启动在Container中,从而完成Driver端分发给Executor的各个任务。将Spark作业跑在Yarn上,首先需要启动Yarn集群,然后通过spark-shell或spark-submit的方式将作业提交到Yarn上运行。
// 需要在spark-env.sh中配置HADOOP_CONF_DIR
HADOOP_CONF_DIR=/opt/hadoop260/etc/hadoop
Yarn的两种模式:
① client
② cluster
可以通过- -deploy-mode 进行指定,也可以直接在 - -master 后面使用 yarn-client和yarn-cluster进行指定
两种模式的区别:
在于driver端启动在本地(client),还是在Yarn集群内部的AM中(cluster)
Spark架构设计
- 运行架构
①在驱动程序中,通过SparkContext主导应用的执行
②SparkContext可以连接不同类型的Cluster Manager(Standalone、YARN、Mesos),连接后,获得集群节点上的Executor
③一个Worker节点默认一个Executor,可通过SPARK_WORKER_INSTANCES调整
④每个应用获取自己的Executor
⑤每个Task处理一个RDD分区
Spark架构核心组件
术语 | 说明 |
---|---|
Application | 建立在Spark上的用户程序,包括Driver代码和运行在集群各节点Executor中的代码 |
Driver program | 驱动程序。Application中的main函数并创建SparkContext |
Cluster Manager | 在集群(Standalone、Mesos、YARN)上获取资源的外部服务 |
Worker Node | 集群中任何可以运行Application代码的节点 |
Executor | 某个Application运行在worker节点上的一个进程 |
Task | 被送到某个Executor上的工作单元 |
Job | 包含多个Task组成的并行计算,往往由Spark Action触发生成,一个Application中往往会产生多个Job |
Stage | 每个Job会被拆分成多组Task,作为一个TaskSet,其名称为Stage |
Spark API
- Maven项目需要导入的依赖
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.8</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.1.1</version>
</dependency>
- SparkContext
①连接Driver与Spark Cluster(Workers)
②Spark的主入口
③每个JVM仅能有一个活跃的SparkContext
④SparkContext对象的获取
// 导包
import org.apache.spark.{SparkConf, SparkContext}
// 配置
val conf=new SparkConf().setMaster("local[2]").setAppName("HelloSpark")
// 获取SparkContext对象
val sc=SparkContext.getOrCreate(conf)
- SparkSession
Spark 2.0+应用程序的主入口:包含了SparkContext、SQLContext、HiveContext以及StreamingContext - RDD
Spark核心,最基本的数据抽象 - Dataset
从Spark1.6开始引入的新的抽象,特定领域对象中的强类型集合,它可以使用函数或者相关操作并行地进行转换等操作 - DataFrame
DataFrame是特殊的Dataset
Spark核心:RDD
RDD概念
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区且里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
RDD的特性
- A list of partitions
一组分区,分区是spark中数据集的最小单位。即spark当中数据是以分区为单位存储的,不同的分区被存储在不同的节点上,这也是分布式计算的基础 - A function for computing each split
一个应用在各个分区上的计算任务。在spark中数据和执行的操作是分开的,并且spark基于懒计算的机制,在真正触发计算的行动操作出现之前,spark会将对哪些数据执行哪些计算存储起来。数据和计算之间的映射关系就存储在RDD中 - A list of dependencies on other RDDs
RDD之间的一组依赖关系。RDD之间存在转化关系,一个RDD可以通过转化操作转化成其他RDD,这些转化操作都会被记录下来,当部分数据丢失的时候,spark可以通过记录的依赖关系重新计算丢失部分的数据,而不是重新计算所有数据 - Optionally, a Partitioner for key-value RDDs
一个分区的方法,也就是计算分区的函数。spark当中支持基于hash的hash分区方法和基于范围的range分区方法 - Optionally, a list of preferred locations to compute each split on
一个列表,存储的内容是存储每个分区的优先存储的位置。
如上所述,可以看出spark一个重要的理念:即移动数据不如移动计算。在spark运行调度的时候,会倾向于将计算分发到节点,而不是将节点的数据搜集起来计算,RDD正是基于这一理念而生的
RDD编程流程
RDD创建
- 使用集合创建RDD
该种方式可使用parallelize
和makeRDD
两种函数
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partition, SparkConf, SparkContext}
object WorldCount {
def main(args: Array[String]): Unit = {
// 获取配置
val conf:SparkConf = new SparkConf().setMaster("local[*]").setAppName("worldCount")
// 获取SparkContext对象
val sc:SparkContext = SparkContext.getOrCreate(conf)
// 使用parallelize函数
val rdd1:RDD[String] = sc.parallelize(List("hi scala","hi java","hi world"))
println("--------parallelize--------")
// worldCount
rdd1.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_).collect.foreach(println)
println("--------makeRDD--------")
val rdd2:RDD[String] = sc.makeRDD(List("hello scala","hello java","hello world"))
rdd2.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_).collect.foreach(println)
}
}
注意*:
①Spark默认会根据集群的情况来设置分区的数量,也可以通过parallelize()第二参数来指定
②Spark会为每一个分区运行一个任务进行处理
③makeRDD底层实际调用的还是parallelize()
- 通过加载文件产生RDD
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partition, SparkConf, SparkContext}
object WorldCount2 {
def main(args: Array[String]): Unit = {
// 获取配置
val conf:SparkConf = new SparkConf().setMaster("local[*]").setAppName("worldCount")
// 获取SparkContext对象
val sc:SparkContext = SparkContext.getOrCreate(conf)
// 使用本地文件系统,windows、linux均可
val rdd3:RDD[String] = sc.textFile("file:///f:/a.txt")
println("--------本地文件--------")
// worldCount
rdd3.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_).collect.foreach(println)
println("--------hdfs--------")
// hdfs分布式文件系统
val rdd4:RDD[String] = sc.textFile("hdfs://hadoop4:9000/user/a.txt")
rdd4.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_).collect.foreach(println)
}
}
- 其他方法
SparkContext.wholeTextFiles()
:可以针对一个目录中的大量小文件返回<filename,fileContent>作为PairRDD
SparkContext.sequenceFile[K,V]()
:Hadoop SequenceFile的读写支持
SparkContext.hadoopRDD()
、newAPIHadoopRDD()
:从Hadoop接口API创建
SparkContext.objectFile()
:RDD.saveAsObjectFile()的逆操作
RDD分区和RDD操作
RDD分区
分区是指RDD拆分后,被并发送到节点的不同块之一
- 拥有的分区越多,得到的并行性就越强
- 每个分区都是被分发到不同Worker Node的候选者
- 每个分区对应一个Task
- 只有Key-Value类型的RDD才有分区的,非Key-Value类型的RDD分区的值是None
- 每个RDD的分区ID范围:0~numPartitions-1,决定这个值是属于哪个分区
RDD操作
RDD的操作分为lazy和non-lazy两种
- Transformation(lazy):也称为转换操作、转换算子
- Actions(non-lazy):立即执行,也称为动作操作、动作算子
RDD转换算子
对于转换操作,仅记录作用于RDD上的操作,当遇到动作算子才会真正计算
常用RDD转换算子–map算子
- 对RDD中的每个元素都执行一个指定的函数来产生一个新的RDD
- 任何原RDD中的元素在新RDD中都有且只有一个元素与之对应
- 输入分区与输出分区一一对应
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("mapDemo")
val sc = SparkContext.getOrCreate(conf)
val rdd1 = sc.parallelize(1 to 10)
val rdd2 = rdd1.map(_*2)
// map把普通RDD变成PairRDD,RDD类型为<k,v>形式
val rdd3 = rdd1.map(x=>(x,1))
rdd2.collect.foreach(println)
rdd3.collect.foreach(println)
}
常用RDD转换算子–mapValue算子
- 原RDD中的Key保持不变,与新的Value一起组成新的RDD中的元素,仅适用于PairRDD
val rdd1 = sc.makeRDD(List("tiger","dog","lion","cat","panther","eagle"))
val rdd2 = rdd1.map(x=>(x.length,x))
rdd2.collect().foreach(println)
val rdd3 = rdd2.mapValues(x=>"_"+x+"_").collect().foreach(println)
常用RDD转换算子–filter算子
对元素进行过滤,对每个元素应用指定函数,返回值为true的元素保留在新的RDD中
val rdd1 = sc.makeRDD(1 to 10)
rdd1.filter(_%2==0).collect.foreach(println)
rdd1.filter(_%2==1).collect.foreach(println)
常用RDD转换算子–distinct算子
val rdd1 = sc.parallelize(List(1,1,2,3,4,4,5,6,8,9,8,8))
val rdd2 = rdd1.distinct
// 指定分区数
val rdd3 = rdd1.distinct(2)
println("rdd1的分区数:"+rdd1.partitions.length)
println("rdd2的分区数:"+rdd2.partitions.length)
println("rdd3的分区数:"+rdd3.partitions.length)
rdd2.collect.foreach(print)
常用RDD转换算子–reduceByKey、groupByKey、sortByKey算子
val rdd1 = sc.makeRDD(List("tiger","dog","lion","cat","panther","eagle"))
val rdd2 = rdd1.map(x=>(x.length,x)).reduceByKey(_+_).collect.foreach(println)
println("-----------groupByKey------")
val rdd3 = rdd1.map(x=>(x.length,x)).groupByKey().collect.foreach(println)
println("---------sortByKey---------")
val rdd4 = rdd1.map(x=>(x.length,x)).sortByKey().collect.foreach(println)
println("---------sortByKey逆序---------")
val rdd5 = rdd1.map(x=>(x.length,x)).sortByKey(false).collect.foreach(println)
常用RDD转换算子–union、intersection算子
val rdd1 = sc.parallelize(1 to 3)
val rdd2 = sc.parallelize(3 to 4)
println("----------union------------")
// 并集不去重
rdd1.union(rdd2).collect.foreach(println)
println("----------++------------")
(rdd1 ++ rdd2).collect.foreach(println)
println("----------intersection------------")
// 取交集
rdd1.intersection(rdd2).collect.foreach(println)
常用RDD转换算子–join、leftOuterJoin、rightOuterJoin算子
val rdd3 = sc.parallelize(List("a","b","c")).map((_,1))
val rdd4 = sc.parallelize(List("c","d","e","f")).map((_,1))
println("----------join------------")
rdd3.join(rdd4).collect.foreach(println)
println("----------leftOuterJoin------------")
rdd3.leftOuterJoin(rdd4).collect.foreach(println)
println("----------rightOuterJoin------------")
rdd3.rightOuterJoin(rdd4).collect.foreach(println)
RDD动作算子
本质上动作算子通过SparkContext执行提交作业操作,触发RDD DAG(有向无环图)的执行。所有的动作算子都是急迫型(non-lazy),RDD遇到Action就会立即计算
RDD常用动作算子–count算子
- 返回的是数据集中的元素的个数
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.count // 6
RDD常用动作算子–collect算子
- 以Array返回RDD的所有元素。一般在过滤或者处理足够小的结果的时候使用
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.collect
RDD常用动作算子–take算子
- 返回前n个元素
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.take(3)
RDD常用动作算子–first算子
- 返回RDD第一个元素
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.first
RDD常用动作算子–reduce算子
- 根据指定函数,对RDD中的元素进行两两计算,返回计算结果
val rdd1 = sc.makeRDD(1 to 5)
rdd1.reduce((x,y)=>{println(x,y);x+y})
rdd1.reduce(_+_)
RDD常用动作算子–foreach算子
- 对RDD中的每个元素都使用指定函数,无返回值
val rdd=sc.parallelize(1 to 100)
rdd.foreach(println)
RDD常用动作算子–lookup算子
- 用于PairRDD,返回K对应的所有V值
val rdd=sc.parallelize(List(('a',1), ('b',2), ('b',3), ('c',4)))
rdd.lookup('b') // WrappedArray(2, 3)
RDD常用动作算子–max、min算子
- 求最大最小值
val rdd=sc.parallelize(1 to 30)
rdd.max //求最大值
rdd.min //求最小值
RDD常用动作算子–saveAsTextFile算子
- 保存RDD数据至文件系统
val rdd1 = sc.makeRDD(1 to 5)
// 存储到hdfs文件系统
rdd1.saveAsTextFile("hdfs://192.168.233.133:9000/user/rdd1.txt")
// 本地文件系统
rdd1.saveAsTextFile("file:///f:/user/rdd1.txt")