目录
目录
一 spark设计与运行原理
1 Spark简介
Spark是由美国加州大学伯克利分校AMP实验室于2009年开发,是基于内存计算的大数据并行框架,可用于构建大型的、低延时的数据分析应用程序。2013年成为Apache下的开源项目。Apache Spark是一种快速通用的集群计算系统。它提供Java,Scala,Python和R中的高级API,以及支持通用执行图的优化引擎。它还支持一组丰富的更高级别的工具,包括Spark SQL用于SQL和结构化数据的处理,MLlib机器学习,GraphX用于图形处理和Spark Streaming。
2 Spark特点
- 运行速度快:Spark主要使用先进的有向无环图DAG执行引擎,以支持循环数据流和内存计算,基于内存的执行速度比Hadoop MapReduce快上百倍,基于磁盘的执行速度也能快十倍。
- 容易使用:Spark支持Scala、Java、R和Python等语言,简洁的API设计有助于用户轻松构建并行程序,并且可以通过Spark Shell进行交互式编程。
- 通用性:Spark提供了完整而强大的技术栈,包括SQL查询、流式计算、机器学习和图算法等组件,这些组件可以无缝整合在同一个应用中,足以应对复杂的计算。
- 运行模式多样:Spark可以运行在独立的集群模式中,也可以运行于Hadoop上,并且可以访问HDFS、HBase、Cassandra和Hive等数据源。
3 Spark生态
大数据处理类型
- 复杂的批量数据处理:时间跨度通常在数十分钟到数小时之间(Hadoop MapReduce);
- 基于历史数据的交互式查询:时间跨度通常在数十秒到数分钟之间(Impala,Hive);
- 基于实时数据流的数据处理:时间跨度通常在数百毫秒到数秒之间(Storm,Spark stream)。
大数据框架存在的一些问题
- 不同软件产生的数据格式不一样,因此无法做到数据无缝连接,需要进行格式转换;
- 不同软件需要有不同的开发人员和维护人员,成本比较高;
- 难以对同一个集群中各个系统进行统一资源调配。
Spark的设计遵循了“一个软件栈满足不同应用场景”的理念,逐渐形成了一套完整的既能提供内存计算框架,也可以支持SQL即席查询、实时流式计算、机器学习和图计算。Spark可以部署在Yarn之上,同时支持批处理、交互式查询和流数据处理。
Spark已经成为伯克利数据分析软件栈BDAS的重要组成部分。
图1 BDAS架构
Spark组件包含Spark Core、Spark SQL、Spark Streaming、MLlib和GraphX等。其功能具体如下:
- Spark Core:它包含Spark最基础和最核心的功能,如内存计算、任务调度、部署模式和故障恢复、存储管理等,主要面向批数据处理。Spark Core建立在统一的抽象RDD之上,使其可以以一致的方式应对不同的大数据处理场景;需要注意的是,Spark Core通常被简称为Spark。
- Spark SQL:Spark SQL是用于结构化数据处理的组件,允许开发人员直接处理RDD,同时也可查询Hive、Hbase等外部数据源。Spark SQL的一个重要特点是其能够统一处理关系表和RDD,使得开发人员不需要自己编写Spark应用程序,开发人员可以轻松地使用SQL命令进行查询,并进行复杂的数据分析。
- Spark Streaming:Spark Streaming是一种流计算框架,可以支持高吞吐量,可容错处理的实时流数据处理,其核心思路就是将流数据分解成一系列短小的批处理作业,每个短小的批处理作业可以使用Spark Core进行快速处理。Spark Streaming支持多种数据输入源,如Kafka、Flume和Tcp套接字等。
- MLlib(机器学习):MLlib提供了常用机器学习算法的实现,包括聚类、分类、回归、协同过滤等,降低了机器学习的门槛,开发人员只要具备一定的理论知识就能进行机器学习方面的工作。
- GraphX(图计算):GraphX是Spark中用于图计算的API,可以认为是Pregel在Spark上的重写和优化,GraphX性能良好,拥有丰富的功能和运算符,能在海量数据上自如的运行复杂的图算法。
二 Spark的安装与运行
2 运行实例
Spark附带了几个示例程序。Scala,Java,Python和R示例都在 examples/src/main目录中。要运行其中一个Java或Scala示例程序,请 bin/run-example <class> [params]在顶级Spark目录中使用。(在幕后,这将调用用于启动应用程序的更通用的 spark-submit脚本)。例如,
./bin/run-example SparkPi 10
您还可以通过Scala shell的修改版本以交互方式运行Spark。这是学习框架的好方法:
./bin/spark-shell --master local[2]
该--master选项指定分布式集群的URL,或者local使用一个线程local[N]在本地运行,或者使用N个线程在本地运行。您应该从使用local测试开始 。有关选项的完整列表,请使用该--help选项运行Spark shell 。
./bin/spark-shell --master spark://hostname:7077
--master参数指master-url,它表明的Spark的运行模式,值可以为如下所示:
- local 使用一个Worker线程本地化运行Spark\
- local[*] 使用与逻辑CPU个数相同数量的线程来本地化运行Spark(逻辑Cpu个数等于物理Cpu个数乘以每个物理Cpu包含的核心数)
- local[K] 使用K个Worker线程来本地化运行Spark(理想情况下,K应该根据运行机器的Cpu核心数来确定)\ spark://host:port Spark采用独立集群模式,连接到指定的Spark集群,默认端口是7077
- yarn-client Spark采用Yarn集群模式,以客户端模式连接Yarn集群,集群的位置可以再HADOOP_CONF_DIR环境变量中找到;当用户提交了作业后,不能关掉Client,Driver Program驻留在Client
- yarn-cluster Spark采用Yarn集群模式,以集群模式连接Yarn集群,集群的位置可以再HADOOP_CONF_DIR环境变量中找到;当用户提交了作业后,就可以关掉Client,作业会继续在Yarn上运行,该模式不适合运行交互类型的作业,常用于企业生产环境
- mesos://host:port Spark采用Mesos集群模式,连接到指定的Mesos集群,默认端口是5050
Spark的运行模式
- local 单机模式,常用于本地开发
- Standalone模式:Spark自带的资源调度管理服务
- Mesos模式:Mesos是另一种资源调度管理框架,可以为运行在它上面的Spark提供服务
- Yarn模式:yarn是Hadoop中用于资源调度管理的框架,它也可以为Spark提供资源调度服务。
3 Spark的交互式操作入门
基本操作
Scala版本
./bin/spark-shell 进入spark的交互界面
1 val textFile = spark.read.textFile("D:/hadoop/spark/hello.txt") //读取DataSet数据集
2 textFile.count() //返回数据集的元素数
3 textFile.first() //返回数据集中第一个元素
4 val linesWithSpark = textFile.filter(line => line.contains("Spark")) //使用过滤器选取满足要求的元素
Python版本
./bin/pyspark 进入spark的交互界面
1 textFile = spark.read.text("D:/hadoop/spark/hello.txt") //读取数据集
2 textFile.count() //返回数据集的元素数
3 textFile.first() //返回第一个元素
4 textFile.filter(textFile.value.contains("Spark")).count() // How many lines contain "Spark"?
4 更多的交互式操作
textFile.map(x=>x.split(" ").size).reduce((a,b)=>Math.max(a,b)) //map计算每行出现的单词数,reduce统计行中单词的最大数
三 RDD的设计与原理
1 RDD编程基础
RDD(弹性分布式数据集)是Spark的核心概念,它是一个只读的、可分区的分布式数据集,这个数据集的全部或部分可以缓存在内存中,可在多次计算重用。
Spark的另一个抽象就是可以用在并行操作的共享变量,默认情况下,当Spark并行运行一个函数作为不同节点上的一组任务时,它会将函数中使用的每个变量的副本发送给每个任务。有时,变量需要跨任务共享,或者在任务和驱动程序之间共享。Spark支持两种类型的共享变量:广播变量,可用于缓存所有节点的内存中的值; 累加器,它们是仅“添加”到的变量,例如计数器和总和。
1.1 初始化Spark
Spark程序必须做的第一件事是创建一个SparkContext对象,它告诉Spark如何访问集群。要创建SparkContext
首先需要构建一个包含有关应用程序信息的SparkConf对象。每个JVM只能激活一个SparkContext。stop()
在创建新的SparkContext之前,您必须使用它。
scala版本
val conf = new SparkConf().setAppName(appName).setMaster(master)
val sc = new SparkContext(conf)
python版本
conf = SparkConf().setAppName(appName).setMaster(master)
sc = SparkContext(conf=conf)
appName指定了应用程序的名称,master指定了程序运行的url(local,mesos,yarn上)。
1.2 使用shell
./bin/spark-shell --master local
./bin/spark-shell --master local --jar code.jar
./bin/spark-shell --master local --packages "com.example.demo"
--master指定运行模式,--jar添加指定jar包,--packages添加依赖库。
1.3 创建RDD
scala版本
textFile函数
spark通过textFile函数从文件系统加载数据到内存并创建RDD,该方法把文件的URI作为参数,这个参数可以是本地文件系统或分布式文件系统,从文件中读取到的每一行内容都成为RDD中的一个元素。
val lines = sc.textFile("D:/out.txt")
输出 lines: org.apache.spark.rdd.RDD[String] = D:/out.txt MapPartitionsRDD[1] at textFile at <console>:24
parallelize函数
通过并行集合数组创建RDD
var array = Array(1,2,3,4,5)
输出array: Array[Int] = Array(1, 2, 3, 4, 5)
var rdd = sc.parallelize(array)
输出rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[8] at parallelize at <console>:26
python版本
textFile()函数用过文件系统生成RDD
distFile = sc.textFile("data.txt")
parallelize()函数并行化生成RDD
data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data)
spark支持从文件系统中生成RDD,还可以从分布式文件系统中生成RDD,例如:hdfs、Cassandra,HBase,Amazon S3等。
spark生成RDD的注意事项:
- 如果是在本地文件系统中,且运行模式为集群时,需把文件拷贝到Worker节点的相同路径中;
- textFile函数的参数支持使用通配符;
- textFile(url,partitionNum):partitionNum可以指定分区,默认情况下spark为每个块创建分区(hdfs中的块大小默认128M),不能创建比块小的分区。
除了文本文件,Spark还支持其他数据格式:
SparkContext.wholeTextFiles
允许您读取包含多个小文本文件的目录,并将它们的全路径名和内容返回。-
sc.hadoopRDD()方式生成RDD
-
sc.sequenceFile()
- sc.
saveAsObjectFile和
SparkContext.objectFile
支持以包含序列化Java对象的简单格式保存RDD。
1.4 RDD操作
基础示例
//加载文件生成RDD
val lines = sc.textFile("D:/hadoop/spark/hello.txt")
//通过转换统计每个RDD元素的长度
val line =lines.map(x=>x.length)
//统计RDD中的字符个数
line.reduce((a,b)=>a+b)
RDD的操作包括转换和行动操作
(1)转换操作
每一次转换都会生成不同的RDD,供给下一个操作使用,RDD的转换是惰性的,在转换过程中不会发生计算,只有当行动开始时,才会发生真正的计算。
常用的RDD转换API
操作 | 含义 |
filter(func) | 筛选出满足函数func的元素,并返回一个新的数据集 |
map(func) | 将每个元素传递到func中,并将结果返回成一个新的数据集 |
flatMap(func) | 与map()相似,但每个输入元素都可以映射到0到多个输出结果 |
groupByKey() | 应用于(K,V)键值对的数据集时,返回一个新的(K,Iterable)形式的数据集 |
reduceByKey(func) | 应用于(K,V)键值对的数据集时,返回一个新的(K,V)形式的数据集,其中每个值是将每个key传递到函数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) | 使用给定的随机数生成器种子,在有或没有替换的情况下对数据的一小部分进行采样。 |
union(otherDataset) | 返回一个新数据集,其中包含源数据集和参数中元素的并集。 |
intersection(otherDataset) | 返回包含源数据集和参数中元素交集的新RDD。 |
distinct([numPartitions])) | 返回包含源数据集的不同元素的新数据集。 |
aggregateByKey(zeroValue)(seqOp,combOp,[ numPartitions ]) | 当调用(K,V)对的数据集时,返回(K,U)对的数据集,其中使用给定的组合函数和中性“零”值聚合每个键的值。允许与输入值类型不同的聚合值类型,同时避免不必要的分配。同样groupByKey ,reduce任务的数量可通过可选的第二个参数进行配置。 |
sortByKey([ascending], [numPartitions]) | 当在K实现Ordered的(K,V)对的数据集上调用时,返回按键按升序或降序排序的(K,V)对的数据集 |
join(otherDataset, [numPartitions]) | 当调用类型(K,V)和(K,W)的数据集时,返回(K,(V,W))对的数据集以及每个键的所有元素对。外连接通过支持leftOuterJoin ,rightOuterJoin 和fullOuterJoin 。 |
cogroup(otherDataset, [numPartitions]) | 当调用类型(K,V)和(K,W)的数据集时,返回(K,(Iterable <V>,Iterable <W>))元组的数据集。此操作也称为groupWith 。 |
cartesian(otherDataset) | 当调用类型为T和U的数据集时,返回(T,U)对的数据集(所有元素对)。 |
pipe(command, [envVars]) | 通过shell命令管道RDD的每个分区,例如Perl或bash脚本。RDD元素被写入进程的stdin,并且输出到其stdout的行将作为字符串的RDD返回。 |
coalesce(numPartitions) | 将RDD中的分区数减少为numPartitions。过滤大型数据集后,可以更有效地运行操作。 |
repartition(numPartitions) | 随机重新调整RDD中的数据以创建更多或更少的分区并在它们之间进行平衡 |
repartitionAndSortWithinPartitions(partitioner) | 根据给定的分区重新分区RDD,并在每个生成的分区中按键对记录进行排序。这比repartition 在每个分区中调用然后排序更有效,因为它可以将排序推送到shuffle机器中。 |
filter函数
var file = sc.textFile("D:/hadoop/spark/hello.txt")
输出file: org.apache.spark.rdd.RDD[String] = D:/hadoop/spark/hello.txt MapPartitionsRDD[13] at textFile at <console>:24
var rdd = file.filter(line=>line.contains("spark"))
输出rdd: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[14] at filter at <console>:25
rdd.foreach(println)
输出
spark
spark1
jhelewrwespark
map函数
var file = sc.textFile("D:/hadoop/spark/hello.txt")
输出file: org.apache.spark.rdd.RDD[String] = D:/hadoop/spark/hello.txt MapPartitionsRDD[13] at textFile at <console>:24
var rdd2 = file.map(line=>line+"123")
输出rdd2: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[15] at map at <console>:25
rdd2.foreach(println)
输出
sfuhfd sf123
sdfsiows123
spark1123
soar123
.........
但是file.map(line=>line.split(" "))中,line.split(" ")每行筛选出的元素会形成Array对象
flatMap函数
flatMap函数经过----->map过程形成Array对象-------->flat过程将每个Array中的元素转换成RDD中的元素
val rdd = sc.parallelize(1 to 5)
rdd.flatMap(x=>(1 to x)).foreach(x=>print(x+" "))
groupByKey函数
groupByKey函数应用于(K,v)键值对的数据集
val text = sc.parallelize(Array(("hadoop",1),("spark",1),("spark",2),("hadoop",4),("flink",1)))
text.groupByKey().foreach(println)
reduceByKey函数
它也是应用于(K,V)形式的数据集,并返回(K,V)形式的数据集。
reduceByKey((a,b)=>a+b)
原始数据("hadoop",1),("is",1),("hadoop",1),("is",1)--->归并为("hadoop",(1,1)),("is",(1,1))--->通过(a,b)=>a+b筛选形成新数据集("hadoop",2),("is",2)
val text = sc.parallelize(Array(("hadoop",1),("spark",1),("spark",2),("hadoop",4),("flink",1)))
text.reduceByKey((a,b)=>a+b).foreach(println)
mapPartitions(func)函数
func为Iterator[T]=>Iterator[U]类型
val conf = new SparkConf().setMaster("local").setAppName("control").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(conf)
val rdd1 = sc.parallelize(1 to 9, 3)
def doubleFunc(iter: Iterator[Int]) : Iterator[(Int,Int)] = {
var res = List[(Int,Int)]()
while(iter.hasNext) {
val cur = iter.next
res .::= (cur,cur*2)
}
res.iterator
}
val rdd2 = rdd1.mapPartitions(doubleFunc)
println(rdd2.collect().mkString)
输出
map和mapPartitions对比:
-
map是对rdd中的每一个元素进行操作;mapPartitions则是对rdd中的每个分区的迭代器进行操作;
-
如果是普通的map,比如一个partition中有1万条数据。那么你的function要执行和计算1万次。使用MapPartitions操作之后,一个task仅仅会执行一次function,function一次接收所有的partition数据。只要执行一次就可以了,性能比较高。如果在map过程中需要频繁创建额外的对象(例如将rdd中的数据通过jdbc写入数据库,map需要为每个元素创建一个链接而mapPartition为每个partition创建一个链接),则mapPartitions效率比map高的多。SparkSql或DataFrame默认会对程序进行mapPartition的优化;
-
如果是普通的map操作,一次function的执行就处理一条数据;那么如果内存不够用的情况下, 比如处理了1千条数据了,那么这个时候内存不够了,那么就可以将已经处理完的1千条数据从内存里面垃圾回收掉,或者用其他方法,腾出空间来吧。
所以说普通的map操作通常不会导致内存的OOM异常。 但是MapPartitions操作,对于大量数据来说,比如甚至一个partition,100万数据,一次传入一个function以后,那么可能一下子内存不够,但是又没有办法去腾出内存空间来,可能就OOM,内存溢出。
mapPartitionsWithIndex(func)函数
func为Iterator[(Int,T)]=>Iterator[U]类型,Int指定了下标
import org.apache.spark.{SparkConf, SparkContext}
object RDDmyCon {
def doubleFunc(index:Int,iter:Iterator[Int]):Iterator[(Int,Int)]={
var res = List[(Int,Int)]()
while(iter.hasNext){
val cur = iter.next
res .::=(index,cur)
}
res.iterator
}
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("control").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(conf)
val rdd1 = sc.parallelize(1 to 9, 3)
val data2 = rdd1.mapPartitionsWithIndex(doubleFunc)
data2.collect().foreach(x=>print(x+" "))
}
}
sample(withReplace,fraction,seed)函数
对RDD中集合内的元素进行采样,第一个参数withReplacement是true表示有放回取样,false表示无放回取样;第二个参数表示取样比例,第三个参数是随机种子。
val rdd = sc.parallelize(1 to 100)
rdd.sample(false,0.4,5).collect().foreach(x=>print(x+" "))
union(otherDataset)函数
otherDataset指其他rdd
import org.apache.spark.{SparkConf, SparkContext}
object UnionRdd {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().set("spark.testing.memory", "2147480000").setAppName("union").setMaster("local")
val sc = new SparkContext(conf)
val data = sc.parallelize(1 to 10)
val data2 = sc.parallelize(11 to 20)
data.union(data2).collect().foreach(x =>print(x+" "))
}
}
intersection(otherDataset)函数
返回两个数据集的交集
def main(args: Array[String]): Unit = {
val conf = new SparkConf().set("spark.testing.memory", "2147480000").setAppName("union").setMaster("local")
val sc = new SparkContext(conf)
val data = sc.parallelize(1 to 10)
val data2 = sc.parallelize(8 to 20)
data.intersection(data2).collect().foreach(x=>print(x+" "))
}
distinct([numPartitions])函数
去除重复的元素
val conf = new SparkConf().set("spark.testing.memory", "2147480000").setAppName("union").setMaster("local")
val sc = new SparkContext(conf)
val data = sc.parallelize(1 to 10)
val data2 = sc.parallelize(8 to 20)
val test = data.union(data2)
test.distinct().collect().foreach(x=>print(x+" "))
println()
aggregate[U](zeroValue: U)(seqOp: Function2[U, T, U], combOp: Function2[U, U, U]): U函数
zeroValue表示初始值,先将zeroValue与各个分区中的元素进行seqOp操作,然后combOp组合不同分区seqOp计算的结果,返回和zeroValue的类型一样的值。
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("control").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(conf)
val rdd1 = sc.parallelize(1 to 6, 2)
val (mul,sum,count) = rdd1.aggregate((1,0,0))((acc,number)=>(acc._1*number,acc._2+number,acc._3+1),(x,y)=>(x._1*y._1,x._2+y._2,x._3+y._3))
print(mul+" "+ sum +" "+count)
}
输出
aggregateByKey(zeroValue)(seqOp, combOp, [numPartitions])函数
aggregateByKey是根据key对元素操作,设定初始值zeroValue,然后将zeroValue与各个分区的元素进行seqOp操作,然后combOp再把seqOp的结果聚合。
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("control").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(conf)
val array = Array((1,2),(1,3),(2,2),(2,3),(3,1))
val rdd = sc.parallelize(array,2)
def seqOp(a:Int,b:Int):Int={
a+b
}
def combineOp(a:Int,b:Int):Int={
a+b
}
val dd = rdd.aggregateByKey(0)(seqOp,combineOp).collect()
dd.foreach(x=>print(x+" "))
}
sortByKey([ascending], [numPartitions])函数
根据key排序
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("control").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(conf)
val array = Array((5,6),(1,3),(2,6),(2,3),(3,1),(1,5),(2,4),(1,8))
val rdd = sc.parallelize(array)
rdd.sortByKey().collect().foreach(x=>print(x+" "))
}
join(otherDataset, [numPartitions])函数
连接数据集,键相同的连接
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("control").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(conf)
val array = Array((5,6),(1,3),(2,6),(2,3),(3,1),(1,5),(2,4),(1,8))
val array2 = Array((4,5),(1,2),(2,5),(1,8))
val rdd = sc.parallelize(array)
val rdd2 = sc.parallelize(array2)
rdd.join(rdd2).collect().foreach(x=>print(x+" "))
}
cogroup(otherDataset, [numPartitions])函数
对两个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。与reduceByKey不同的是针对两个RDD中相同的key的元素进行合并。
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("control").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(conf)
val array = Array((5,6),(1,3),(2,6),(2,3),(3,1),(1,5),(2,4),(1,8))
val array2 = Array((4,5),(1,2),(2,5),(1,8))
val rdd = sc.parallelize(array)
val rdd2 = sc.parallelize(array2)
rdd.cogroup(rdd2).collect().foreach(x=>print(x+" "))
}
cartesian(otherDataset)函数
对两个RDD中的所有元素进行笛卡尔积操作
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("control").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(conf)
val rdd1 = sc.parallelize(1 to 3)
val rdd2 = sc.parallelize(2 to 4)
rdd1.cartesian(rdd2).foreach(println)
}
pipe(command, [envVars])函数
调用命令
coalesce(numPartitions,shuffle)函数
对RDD进行重新分区,shuffle默认值为false,当shuffle=false时,不能增加分区数目。
repartition(numPartitions)
等于coalesce(numPartitions,true),重新分区
glom():将RDD的每个分区中类型为T的元素转换为Array[T]。
1.3行动操作
行动操作是真正发生计算的地方。
常用的行动操作API
操作 | 含义 |
count() | 返回数据集中的元素个数 |
collect() | 以数组的形式返回数据集中的所有元素 |
first() | 返回数据集中的第一个元素 |
take(n) | 以数组的方式返回数据集中的前n个元素 |
reduce(func) | 通过函数func(输入两个参数) |
foreach(func) | 将数据集中的每个元素传递给func |
takeSample(withReplacement, num, [seed]) | 返回一个数组,其中包含数据集的num个元素的随机样本,有或没有替换,可选地预先指定随机数生成器种子。 |
takeOrdered(n, [ordering]) | 使用自然顺序或自定义比较器返回RDD 的前n个元素。 |
saveAsTextFile(path) | 将数据集的元素作为文本文件(或文本文件集)写入本地文件系统,HDFS或任何其他Hadoop支持的文件系统的给定目录中。Spark将在每个元素上调用toString,将其转换为文件中的一行文本。 |
saveAsSequenceFile(path) (Java and Scala) | 将数据集的元素作为Hadoop SequenceFile写入本地文件系统,HDFS或任何其他Hadoop支持的文件系统中的给定路径中。这可以在实现Hadoop的Writable接口的键值对的RDD上使用。在Scala中,它也可以在可隐式转换为Writable的类型上使用(Spark包括基本类型的转换,如Int,Double,String等)。 |
saveAsObjectFile(path) (Java and Scala) | 使用Java序列化以简单格式编写数据集的元素,然后可以使用它进行加载SparkContext.objectFile() 。 |
countByKey() | 仅适用于类型(K,V)的RDD。返回(K,Int)对的散列映射,其中包含每个键的计数。 |
1.4持久化
在spark中,RDD采用惰性求值的机制,每次遇到行动操作都会从头开始执行计算。每次调用行动操作,都会触发一次从头开始的计算,这对于迭代计算代价是很大的,因为迭代计算经常会多次重复使用同一组数据。解决方法就是通过持久化来避免重复计算的开销。
具体方法就是使用persist()方法,对RDD标记为持久化,出现persist()的地方,并不会马上计算生成RDD并把它持久化,而是要等到第一个行动操作触发真正计算以后,才会把计算结果持久化,持久化后的RDD会被保留在计算节点的内存中,被后续的行动操作重复使用。
persist持久化级别
- persist(MEMORY_ONLY):表示将RDD作为反序列化的对象存储于JVM中,如果内存不足,就要按照LRU原则替换缓存中的内容。
- persist(MEMORY_AND_DISK):表示将RDD作为反序列化的对象存储在JVM中,如果内存不足,超出的分区将会被存放在磁盘上
- 一般使用cache()方法时,会调用persist(MEMORY_ONLY)方法。
1.5 分区
分区的作用:RDD是弹性分布式数据集,通常RDD很大,会被分成很多个分区,分别保存在不同的节点上。对RDD进行分区,目的是增加并行度,让计算向数据靠拢,减少了网络开销。
分区原则:RDD分区的一个原则就是使分区的个数尽量等于集群中的CPU核心数目。对于不同的Spark部署模式,都可以设置分区数目spark.default.parallelism这个参数的值,来配置默认的分区数。
Spark部署模式
- Local模式:默认为本地机器的CPU数目,若设置了local[N],则默认为N;
- Standalone或YARN模式:在集群中所有CPU核心数目总和和2取较大者为默认值;
- Mesos模式:默认分区数为8
手动设置分区数目
- sc.textFile(url,partitionNum)
- sc.parallelize(url,partitionNum) 这个函数若没有指定分区数时,会采用min(default,2)的算法设置分区\
- 使用repartition()方法重新设置分区个数
- data.repartition(partitionNum)
自定义分区方法:Spark提供了自带的哈希分区(HashPartitioner)与区域分区(RangePartitioner),能够满足大多说应用场景的需求。与此同时,Spark也支持自定义分区方式,即提供自定义的Partitioner对象来控制RDD的分区方式,从而利用领域知识进一步减少通信开销。其次,Spark的分区函数针对的是(key,value)类型的RDD,也就是说,RDD的每个元素都是(key,value)类型,分区函数根据key进行分区。因此对于非(key,value)类型的RDD进行分区,需要先把RDD转换为(key,value)类型,再进行分区。
自定义分区类需要继承org.apache.spark.Partitioner类,实现下面三个方法
- numPartitions:Int 返回创建出来的分区数
- getPartition(key:Any):Int 返回给定键的分区编号(0~numPartitions-1)
- equals():java判断相等性的标准方法
WordCount实例图
val file = "hdfs://mynode01:9000/opt/hello.txt"
var conf = new SparkConf().setAppName("WordCount").setMaster("local[4]")
conf.set("spark.testing.memory", "2147480000")
var sc = new SparkContext(conf)
var text = sc.textFile(file)
var wordcount = text.flatMap(line=>line.split(" ")).map(word=>(word,1)).reduceByKey((a,b)=>a+b)
`wordcount.foreach(println)
1.6键值对RDD
1 键值对RDD的创建
键值对RDD的创建方式主要有两种:
- 从文件中加载生成RDD
- 通过并行集合(数组)创建RDD
从文件中加载生成RDD: textFile().map()或textFile().flatMap()
通过并行集合生成RDD:parallelize().map或parallelize().flatMap()
1.7常用的键值对转换操作
reduceByKey(func)
使用func函数合并具有相同键的值。
reduceByKey与groupByKey不同,reduceByKey对每个key对应的值的聚合,而groupByKey生成的是(key,value-list)
groupByKey()
对相同的键进行分组
例("hadoop",1),("spark",2),("hadoop",3),("spark",1)分组后为("hadoop",(1,3)),("spark",(1,2))keys 返回RDD中(K,v)格式数据集中的键
values 返回RDD中(K,v)格式数据集中的值
sortByKey() 返回提个根据key排序的RDD
sortBy(func) 返回按func排序后的RDDmapValues(func) 对RDD中的(K,v)中的v进行func操作
join() 表示内连接,对于给定的两个输入数据集(K,V1)和(K,V2),只有两个数据集都存在的key才会被输出,得到(K,(V1,V2))的结果
combineByKeycombineByKey(createCombiner,mergeValue,mergeCombiners,partitioner,mapSideCombine)中的各个参数的含义如下:\
createCombiner:在第一次遇到key时创建组合函数,将RDD数据集中的V类型值转换C类型值(V=>C);