1、创建工程
在这里添加 spark core包,添加bulid 插件。
<groupId>com.ypl.bigdata</groupId> // 这里添写包名称
<artifactId>spark-200226</artifactId> // 这里是项目名称
<version>1.0-SNAPSHOT</version>//版本号 可以
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
<build>
<finalName>WordCount</finalName>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.3</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
算子总结
map 算子
主要是做数据结构的转换,数据条数不变。
//创建
scala> var source = sc.parallelize(1 to 10)
// 打印
scala> source.collect()
// 将所有元素*2
scala> val mapadd = source.map(_ * 2)
// 打印最终结构
scala> mapadd.collect()
mapPartitions(func)
对分区数据进行转换。将某一个分区的所有数据拿过来形成一个可迭代的集合要求 返回可迭代的集合。提高效率会使用它。
应用场景:
只对分区内数据进行数据。缺点是不释放,可能导致oom。
当内存空间较大的时候建议使用 mapPartitions()以提高效率。
val rdd = sc.parallelize(Array(1,2,3,4))
rdd.mapPartitions(x=>x.map(_*2)) // 是每个元素 * 2组成新的rdd
res3.collect
mapPartitionsWithIndex(func)
获取分区索引,进行操作。
val rdd = sc.parallelize(Array(1,2,3,4))
val indexRdd = rdd.mapPartitionsWithIndex((index,items)=>(items.map((index,_)))) //使每个元素跟所在分区形成一个元组组成一个新的RDD
indexRdd.collect
flatMap
将一个整体拆成一个个 个体。扁平化
val sourceFlat = sc.parallelize(1 to 5)//(1,2,3,4,5)
sourceFlat.collect()
// (1->1, 2->1 ,2 ....5 -> 1,2,3,4,5)
val flatMap = sourceFlat.flatMap(1 to _)
flatMap.collect()
// Array(1,1,2,1,2,3,1,2,3,4,1,2,3,4,5)
glom
将一个分区形成一个数组,形成新的RDD类型时,RDD[Array[T]]
例如:创建一个4个分区的RDD,将每个分区的数据放到一个数组。
求分区的最大值,最小值等等。
val rdd = sc.parallelize(1 to 16 ,4)
//将每个分区的数据放到一个数组并收集到 Driver 端打印
rdd.glom().collect()
groupBy(func)
按照传入函数的返回值进行分组。相同的key对应的值放入一个迭代器。
例如:将元素模以2的值进行分组。
val rdd =sc.parallelize(1 to 4)
val group = rdd.groupBy(_%2)
group.collect
filter(func)
过滤,返回一个新的RDD,该RDD 由经过的 func 函数计算后返回值为true 的元素组成。
例如: 创建一个RDD(由字符串组成)过滤出一个新RDD(包含"xiao"字符串)
var sourceFilter = sc.parallelize(Array("zhang1","zhangy2","zhang3","lisi"))
val filter = sourceFilter.filter(_.contains("zhang"))
filter.collect()
sample(withReplacement,fraction,seed)
以指定的随机种子随机抽样出数量为fraction 的数据,withReplacement 表示抽出的数据是否放回,true 为有放回的抽样,false 为无放回的抽样,seed 用于指定随机数生成器的种子
val rdd = sc.parallelize(1 to 10)
rdd.collect()
var sample1 = rdd.sample(true,0.4,2)
sample1.collect()
// 不放回抽样
var sample2 = rdd.sample(false,0.2,3)
sample2.collect
disitnct([numPartitions])
对源RDD进行去重后返回一个新的RDD.
distinct task 是4/4 map 的task 是 2/2
首先是 write 任务-> 然后 read 任务
shuffer 会慢,然后有IO 的过程
val distinctRdd = sc.parallelize()
// 不指定并行度
val unionRDD = distinctRdd.distinct()
//打印去重后生成新的RDD
unionRDD.collect()
// 指定并行度
val unionRDD = distinctRdd.distinct(2)
coalesce(numPartitions)
缩减分区数,用于大数据集过滤后,提高小数据集的执行效率
val rdd = sc.parallelize(1 to 16 ,4)
// 查看rdd 分区数
rdd.partitions.size
// 对 RDD 重新分区
val coalesceRDD = rdd.coalesce(3)
// 查看rdd 分区数
coalesceRDD.partitions.size
repartition(numPartition)
根据分区数,重新通过网络随机洗牌所有数据
例如: 创建一个 4个分区的RDD,对其重新分区。
val rdd = sc.parallelize(1 to 16 ,4)
rdd.partitions.size
rdd.repartition(2)
rdd.partitions.size
rdd.glom.collect
coalesce 和repartition 的区别,coalesce 重新分区,可以选择是否 shuffle 过程,由参数 shuffle:Boolean = false /true 决定。
repartition 实际上是调用的coalesce ,默认是进行shuffle。
sortBy()
对数据进行排序
val rdd = sc.parallelize(List(2,1,3,5))
rdd.sortBy(x=>x).collect() // 降序
rdd.sortBy(x=>x ,false).collect() // 升序
双value类型交互
union(otherDataset)
对源RDD 和参数 RDD 求并集后返回一个新的RDD
val rdd1 = sc.parallelize(1 to 5)
val rdd2 = sc.parallelize(5 to 10)
val rdd3 = rdd1.union(rdd2)
rdd3.collect()
subtract(otherDataset) 案例
计算差的一种函数,去除两个RDD中相同的元素,不同的RDD 将保留下来
val rdd1 = sc.parallelize(3 to 8)
val rdd2 = sc.parallelize(1 to 5)
val rdd3 = rdd1.substract(rdd1).collect()
intersection(otherDataset) 案例
计算交集的一种函数,保留两个rdd 相同的元素
val rdd1 = sc.parallelize(1 to 7)
val rdd2 = sc.parallelize(5 to 10)
val rdd3 = rdd1.intersection(rdd2)
cartesian(otherDataset)
两个rdd 的笛卡尔积
val rdd1 = sc.parallelize(1 to 3 )
val rdd2 = sc.parallelize(2 to 5)
val rdd3 = rdd1.cartesian(rdd2).collect()
zip(otherDataset) 案例
对两个rdd 进行拉链操作,保证分区数和分区数里的数据量相同。
val rdd1 = sc.parallelize(Array(1,2,3),3 )
val rdd2 = sc.parallelize(Array("a","b","c"),3 )
val rdd3 = rdd1.zip(rdd2).collect
Key-Value类型交互
partitionBy
对pairRDD 进行分区操作,如果原有的partitionRDD和现有的partitionRDD是一致的的话,就不进行分区,否则生成shffuleRDD,即产生shuffle 过程。
例如: 创建一个有四个分区的rdd,对其重新分区。
val rdd1 = sc.parallelize(Array((1,"aaa"),(2,"bbb"),(3,"ccc"),(4,"ddd")),4 )
rdd.partitions.size
var rdd2 = rdd.partitionBy(new org.aparche.spark.HashPartitioner(2))
// 自定义分区规则
val config:SparkConf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
// 创建Spark 上下文
val sc = newSpakrContext(config)
//
val listRDD = sc.makeRDD(List(("a",1),("b",2),("c",3)))
val partRDD = listRDD.patitionBy(new MyPartitioner(3))
partRDD.saveAsTextFile("output")
// 声明分区器
// 继承Partitioner 类
class MyPartitioner(partitions: Int) extends Partitioner{
override def numPartitions: Int = {
pattitions
}
override def getPartition(key:Any):Int = {
1
}
}
groupByKey
groupByKey 也是对每个key进行操作,但只生成一个 sequence。
创建一个pairRDD,将相同key值聚合到一个sequence中并计算相同key对应值的相加的结果。
val words = Array("1","2","2","3","3","3")
val wordPairsRDD = sc.parallelize(words).map(word => (word,1))
val group = wordPairsRDD.groupByKey()
group.collect()
group.map(t=> (t._1,t._2.sum)).collect
reduceByKey(func,[numTasks]) 案例
在一个(k,v)的rdd 上调用,返回一个(k,v)的rdd,使用指定的 reduce函数,将相同的key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。
例如:创建一个pairRDD 计算相同key对应值的相加的结果。
val rdd = sc.parallelize(List(("female",1),("male",5),("female",5),("male",2)))
val reduce = rdd.reduceByKey((x,y=> x + y))
reduce.collect()
reduceByKey 和 groupByKey 的区别:
前者是按照key进行聚合在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。
按照key 进行分组,直接进行shuffle。
reduceByKey 比 groupByKey 用多,也要注意业务逻辑上的区别。
aggregateByKey 案例
参数:(zeroValue:U,[partitioner:Partitioner])(seqOp:(U,V)=>U,combOp:(U,U)=>U)
在 kv对的rdd 中,按 key将 value 进行分组合并,合并时,将每个value和初始值作为seq函数的参数进行计算,返回的结果作为一个新的kv对,然后将结果按照 key 进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value 传给combine 函数,以此类推),将key与计算结果作为一个新的kv对 输出。
zeroValue:给每一个分区中的每一个key一个初始值。
seqOp:分区内运算规则
combOp:分区间运算规则
例如:创建一个pairRDD,取出每个分区相同key 对应值的最大值,然后相加。
// 创建parirdd
val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
// 查看分区情况
rdd.glom.collect
val agg = rdd.aggregateByKey(0)(math.max(_,_),_+_)
agg.collect
拓展:
val agg = rdd.aggregateByKey(10)(math.max(_._),_+_).collect
Array((a,10),(b,10),(c,20))
val agg = rdd.aggregateByKey(10)(_+_,_+_).collect
foldByKey
底层代码和 aggregateByKey 都调用了一个方法,,seqop和combop相同。
// 创建parirdd
val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
// 查看分区情况
rdd.glom.collect
val agg = rdd.foldByKey(0)(_+_)
agg.collect
combineByKey[C] 案例
参数:(createCombine:V=>C,mergeValue:(C,V)=>C,mergeCombiners:(C,C)=>C)
对于相同的K,把 V 合并成一个集合
- createCombiner:combineByKey() 会遍历分区中的所有元素,因此每个元素的键会被初始化,创建对于累加器的初始值。
2)mergeValue:对之前分区内处理的键进行累加,和新的值进行合并。
3)mergeCombiners: 对分区间的数据进行累加。
val input = sc.parallelize(Array(("a",88),("b",95),("a",91),("b",93),("a",95),("b",98)),2)
val combine = input.combineByKey(
(_,1),
(acc:(Int,Int),v)=>(acc._1+v,acc._2+1),
(acc1:(Int,Int),acc2:(Int,Int))=>(acc1._1+acc2._1,acc1._2+acc2._2,))
)
combine.collect
val result = combine.map{case(key,value)=>(key,value._1/value._2.toDouble)}
result.collect()
sortByKey([ascending],[numTasks]) 案例
作用在一个(k,v)的 RDD 上调用,K必须实现Order 接口,返回一个按照 key进行排序的(k,v) 的 RDD.
val rdd = sc.parallelize(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))
rdd.sortBykey(true).collect()
rdd.sortBykey(false).collect()
mapValues 案例
针对(k,v)形式的类型只对V 进行操作
例如创建一个pairRDD,并将value 添加字符串 “||||”
val rdd3 = sc.parallelize(Array(1,"a"),(1,"d"),(2,"b"),(3,"c"))
rdd3.mapValues(_+"|||").collect()
join(otherDataset,[numTasks]) 案例
在类型(k,v) 和 (k,w) 的RDD 上调用,返回一个相同key 对应的所有元素堆在一起的(k,(v,w))的 RDD
例如:创建两个pariRDD,并将相同的数据聚合到一个元组。
val rdd1 = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
val rdd2 = sc.parallelize(Array((1,3),(2,4),(3,9)))
rdd1.join(rdd2).collect()
Array[(Int,(String,Int))] = Array((2,(b,5)),(1,(a,4)),(3,(c,6)))
cogroup 案例
在类型为(k,v)和(k ,w )的 rdd 上调用,返回一个(l,(Iterable,Iterable))类型的RDD
创建两个pairRDD,将key相同的数据聚合到一个迭代器。
val rdd1 = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
val rdd2 = sc.parallelize(Array((1,3),(2,4),(3,9)))
rdd1.cogroup(rdd1).collect()
cogroup 可以把不是共同存在的元素,展示出来,join 不行。
案例,每个省份的广告统计。
样例:
package com.ypl.bigdata.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Practice {
def main(args: Array[String]): Unit = {
// 1初始化spark 配置信息建立与spark的链接
val sparkconf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("Test")
val sc = new SparkContext(sparkconf)
// 2、读取数据生成RDD : TS, Province,City,User,AD
val line = sc.textFile("xxx/agent.log")
// 3、按照最小力度进行聚合;((Province,AD),1)
val proAdANDOne: RDD[((String, String), Int)] = line.map(x => {
val fields: Array[String] = x.split(" ")
((fields(1), fields(4)), 1)
}
)
// 4、 计算每个广告点击的总数 ;((Province,AD),sum)
val proAdANDsum: RDD[((String, String), Int)] = proAdANDOne.reduceByKey(_ + _)
// 5、 将省份作为 key,广告+ 点击次数为 value (province,(AD,sum))
val proANDAdsum: RDD[(String, (String, Int))] = proAdANDsum.map(x => (x._1._1, (x._1._2, x._2)))
// 6、将同一省的广告进行聚合
val provinceGroup: RDD[(String, Iterable[(String, Int)])] = proANDAdsum.groupByKey()
// 7 排序、取前三条、打印
val provinceAdTop3: RDD[(String, List[(String, Int)])] = provinceGroup.mapValues(
x => x.toList.sortWith((x, y) => x._2 > y._2).take(3)
)
provinceAdTop3.collect().foreach(println)
// 关闭连接
sc.stop()
}
}
Action
reduce
通过func 函数聚集的RDD 中所有元素,先聚合分区内数据,再聚合分区间数据。
例:聚合RDD[Int] 、RDD[String] 中的所有元素
val rdd1 = sc.makeRDD(1 to 10 ,2)
rdd1.reduce(_+_)
val rdd1 = sc.makeRDD(Array(("a",1),("a",3),("c",3),("d",5)))
rdd2.reduce((x,y=>(x._1 + y._1, x._2+y._2)))
(adca,12)
collect()
在驱动程序中,以数组的形式返回数据集中的所有元素
count()
返回rdd 中元素的个数。
first()
返回 rdd 第一个元素
take(n)
取出rdd中第第几个元素
takeOrdered(3)
排序完后取出第几个元素
aggregare()
创建一个rdd ,将所有元素相加
val rdd1 = sc.makeRDD(1 to 10,2)
rdd1.aggregate(0)(_+_,_+_) //55
rdd1.aggregate(10)(_+_,_+_) // 85 2 个分区 10 + 分区间计算 10
fold(num)(func) 案例
折叠操作,aggregare 的简化操作,seqop和combop 一样
val rdd1 = sc.makeRDD(1 to 10,2)
rdd.fold(0)(_+_)
saveAsTextFile(path)
将数据集的元素以textfile的形式保存到HDFS文件系统或者其支持的文件系统,对于每个元素,Spark 将会调用 toString方法,将它转换为文件中的文本。
saveAsSequenceFile(path)
将数据集中的元素以Hadoop sequencefile 的格式保存到指定目录下,可以使HDFS或者其他Hadoop 支持的文件系统。
saveAsObjectFile(path)
将RDD 中的元素序列化对象,存储到文件当中。
countByKey() 案例
针对(k,v) 类型的rdd 返回一个(k,Int) 的map ,表示每一个key对应的元素个数。
val rdd1 = sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)),3)
rdd.countByKey
foreach(func) 案例
在数据集的每一个元素上,运行函数func 进行更新