目录
一、什么是RDD
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集(弹性:内存不足,自动写入磁盘),是Spark中最基本的数据(计算)抽象(抽象:不存数据)。代码中是一个抽象类,它代表一个不可变、可分区、里面的元素可并行计算的集合。
RDD五大特性
- RDD是由一系列的partition组成的。
- 函数是作用在每一个partition(split)上的。
- RDD之间有一系列的依赖关系。
- 分区器是作用在K,V格式的RDD上。
- RDD提供一系列最佳的计算位置。
RDD特点
RDD表示只读的分区的数据集,对RDD进行改动,只能通过RDD的操作算子转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。
分区:RDD逻辑上是分区的,每个分区分布在不同节点上的,分区的数据是抽象存在的,计算的时候会通过一个compute函数得到每个分区的数据。如果RDD是通过已有的文件系统构建,则compute函数是读取指定文件系统中的数据,如果RDD是通过其他RDD转换而来,则compute函数是执行转换逻辑将其他RDD的数据进行转换。
只读:如下图所示,RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD。由一个RDD转换到另一个RDD,可以通过丰富的操作算子实现,不再像MapReduce那样只能写map和reduce了
RDD的操作算子包括两类,一类叫做transformations,它是用来将RDD进行转化,构建RDD的血缘关系;另一类叫做actions,它是用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中
依赖:RDDs通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种,一种是窄依赖,RDDs之间分区是一一对应的,另一种是宽依赖,下游RDD的每个分区与上游RDD(也称之为父RDD)的每个分区都有关,是多对多的关系。
缓存:如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。如下图所示,RDD-1经过一系列的转换后得到RDD-n并保存到hdfs,RDD-1在这一过程中会有个中间结果,如果将其缓存到内存,那么在随后的RDD-1转换到RDD-m这一过程中,就不会计算其之前的RDD-0了。
CheckPoint:虽然RDD的血缘关系天然地可以实现容错,当RDD的某个分区数据失败或丢失,可以通过血缘关系重建。但是对于长时间迭代型应用来说,随着迭代的进行,RDDs之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。为此,RDD支持checkpoint将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,因为checkpoint后的RDD不需要知道它的父RDDs了,它可以从checkpoint处拿到数据。
二、RDD编程模型
在Spark中,RDD被表示为对象,通过对象上的方法调用来对RDD进行转换。经过一系列的transformations定义RDD之后,就可以调用actions触发RDD的计算,action可以是向应用程序返回结果(count, collect等),或者是向存储系统保存数据(saveAsTextFile等)。在Spark中,只有遇到action,才会执行RDD的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换。
要使用Spark,开发者需要编写一个Driver程序,它被提交到集群以调度运行Worker。Driver中定义了一个或多个RDD,并调用RDD上的action,Worker则执行RDD分区计算任务。
1、RDD三种创建方式
(1)从集合中创建RDD,Spark主要提供了两种函数:parallelize和makeRDD (java代码见案例1)
val rdd = sc.parallelize(Array(1,2,3,4,5,6,7,8)) //并行
val rdd1 = sc.makeRDD(Array(1,2,3,4,5,6,7,8)) //底层就是parallelize
val source = sc.parallelize((1 to 10),2) //2是自定义的分区数量
object SparkOperation{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark").setMaster("local[*]")
val sc = new SparkContext(conf)
//1、从内存创建
val source = sc.parallelize((1 to 10),2)
val source2 = sc.makeRDD(Array(1, 2, 3, 4), 3)
//2、外部创建
val value = sc.textFile("in")
val res = value.flatMap(_.split(",")).map((_,1)).reduceByKey(_+_)
// source.saveAsTextFile("out")
res.saveAsTextFile("out2")
}
}
(2)由外部存储系统的数据集创建:包括本地的文件系统,Hadoop支持的数据集,比如HDFS、Cassandra、HBase等
val rdd2= sc.textFile("hdfs://hadoop102:9000/RELEASE")
(3)从其他RDD创建
三、RDD的转换(面试开发重点)
RDD整体分为Value类型和Key-Value类型
1、value类型
1、map(func)案例 (java代码见案例2)
作用:返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
需求:创建一个1-10数组的RDD,将所有元素*2形成新的RDD
object Demo1{
def main(args: Array[String]): Unit = {
//将1-10各元素*2
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
//map算子
val unit = sc.makeRDD(1 to 10)
val unit2 = sc.makeRDD(Array(1,2,3,4))
val res = unit.map(x=>x*2) //x=>x*2是spark的计算
val res2 = unit2.map(x=>x*2)
res.collect().foreach(println)
res2.collect().foreach(print)
}
}
2、mapPartitions(func) 案例
作用:类似于map,但独立地在RDD的每一个分片上运行,在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]。若有N个元素,有M个分区,那么map函数将被调用N次,而mapPartitions被调用M次,一个函数一次处理所有分区。
需求:创建一个RDD,使每个元素*2组成新的RDD
//mappartiton对每一个分区进行计算 map对每一个元素进行计算
//mappartiton效率由于map算子,减少了发送执行器执行交互次数
//mappartiton可能存在内存溢出(OOM)
val res3 = unit.mapPartitions(datas => {
datas.map(x=>x*2) //这一行整体对应一个executer, x=>x*2属于scala的计算,不属于spark
})
res3.collect().foreach(println)
3、mapPartitionsWithIndex(func) 案例
object mapparindex{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD(1 to 10)
// mapPartitionsWithIndex在mapPartitions基础上加了分区号,{}是模式匹配
val tupleRDD = unit.mapPartitionsWithIndex {
case (num, datas) => {
datas.map((_, "分区号是:" + num))
}
}
tupleRDD.collect().foreach(println)
}
}
4、flatMap
作用:类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
object flatmap{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD(Array(List(1,2),List(3,4)))
val value: RDD[Any] = unit.flatMap(x=>x)
val value1: RDD[List[Any]] = unit.map(x=>x)
//flatmap 将每一个元素拆开压平
value.collect().foreach(println) // 1 2 3 4
value1.collect().foreach(println) //List(1, 2) List(3, 4)
}
}
map和flatMap区别
object test {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamWordCount")
val sc = new SparkContext(sparkConf)
val value: RDD[String] = sc.textFile("in/in2.txt")
//flatMap():将整个文件数据切分打散,返回RDD[String],即每一个单词各自独立
val unit: RDD[String] = value.flatMap(_.split(","))
unit.collect().foreach(println)
//map():将整个文件数据切分,返回RDD[Array[String]],即返回一个所有单词组成的整体数组
val unit1: RDD[Array[String]] = unit.map(_.split(","))
unit1.collect().foreach(array=>println(array.mkString("-")))
}
}
5、glom案例
作用:将每一个分区形成一个数组,形成新的RDD类型时RDD[Array[T]]
object glom{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD(1 to 10)
// 将一个分区的数据放在一个array
val glomRDD: RDD[Array[Int]] = unit.glom()
glomRDD.collect().foreach(array =>println(array.mkString(",")+" 每个分区最大值:"+array.max))
}
}
6、groupby分组
object groupby{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD(1 to 10)
//分组后得到一个对偶元组<k,v> k是分组的值,v是分组的数据集合
val groupbyRDD: RDD[(Int, Iterable[Int])] = unit.groupBy(_%3)
groupbyRDD.collect().foreach(println)
}
}
7、filter(func) 案例
作用:过滤。返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成。
object filter{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD(Array("xincm","xinjn","wang","wx","xincongm"))
unit.filter(_.contains("xin")).collect().foreach(println)
}
}
8、sample(withReplacement, fraction, seed) 案例
作用:以指定的随机种子随机抽样出数量为fraction的数据,withReplacement表示是抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样,seed用于指定随机数生成器种子,类似java的random,fraction:数量(大致),当false时:fraction只能取0-1,表示百分比,当true时:0-1不会重复数据,1以上会重复数据
object sample{
//抽样
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD(1 to 10)
// 参数:1、是否放回 2、打分(大于这个分获取)false:0-1,true:0-1表示不重复,1以上重复 3、种子生成器 类似random
unit.sample(true,7,1).collect().foreach(println)
unit.sample(false,0.5,1).collect().foreach(println)
}
}
Java随机数random
import java.util.Random;
public class RandomOper {
public static void main(String[] args) {
Random random = new Random(10);
for(int i=0;i<10;i++){
System.out.println(random.nextInt());
}
// 重新new random
random = new Random(10);
for(int i=0;i<10;i++){
System.out.println(random.nextInt());
}
// 你会发现输出的值一样,因为生成random算法一样,根据输入的种子决定,真正的随机数seed=System.currentTimeMillis()
}
}
9、distinct([numTasks])) 案例 shuffle机制
作用:对源RDD进行去重返回新的RDD。默认有8个并行任务来操作,但是可以传入一个可选的numTasks参数改变它。
object distinct{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD(Array(1,2,3,1,2,6,9,8,5,0));
//去重输出,存在shuffle机制:打乱重组到其他分区,distinct(2)指定去重后的分区数量
//没有shuffle的速度快,分区之间不用等待,相互独立
unit.distinct().collect().foreach(println)
unit.distinct(2).collect().foreach(println)
}
}
10、coalesce(numPartitions,shuffle) , repartition(numPartitions)案例
coalesce:缩减分区数,用于大数据集过滤后,提高小数据集的执行效率。 合并分区
repartition:根据分区数,重新通过网络随机洗牌所有数据
区别:coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定。 repartition实际上是调用的coalesce,默认是进行shuffle的。
object coalesce{
//缩减分区(合并分区),缩减后的分区必须小于之前分区数,否则分区数不变
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD((1 to 10),4);
System.out.println("前分区数:"+unit.partitions.size)
val coalUnit: RDD[Int] = unit.coalesce(3)
// val coalUnit: RDD[Int] = unit.coalesce(3,shuffle = true)
System.out.println("后分区数:"+coalUnit.partitions.size)
//repartiton 重新洗牌,分区数可以大于原分区数
val repartitionUnit: RDD[Int] = unit.repartition(6)
System.out.println("repartition分区数: "+repartitionUnit.partitions.size)
}
}
11、sortBy(func,[ascending], [numTasks]) 案例 (打乱分区)
作用:使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序。
object sortBy{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val unit = sc.makeRDD(List(5,6,4,0,3,9,4,1),2);
// 根据大小排序,默认升序
val res: RDD[Int] = unit.sortBy(x=>x)
res.collect().foreach(print) //01344569
unit.sortBy(x=>x%3).collect().foreach(print) //60394415
}
}
12、pipe(command, [envVars]) 案例
作用:管道,针对每个分区,都执行一个shell脚本,返回输出的RDD。注意:脚本需要放在Worker节点可以访问到的位置
需求:编写一个脚本,使用管道将脚本作用于RDD上。
(1)编写一个脚本
Shell脚本
#!/bin/sh
echo "AA"
while read LINE; do
echo ">>>"${LINE}
done
(2)创建一个只有一个分区的RDD
scala> val rdd = sc.parallelize(List("hi","Hello","how","are","you"),1)
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[50] at parallelize at <console>:24
(3)将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()
res18: Array[String] = Array(AA, >>>hi, >>>Hello, >>>how, >>>are, >>>you)
(4)创建一个有两个分区的RDD
scala> val rdd = sc.parallelize(List("hi","Hello","how","are","you"),2)
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[52] at parallelize at <console>:24
(5)将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()
res19: Array[String] = Array(AA, >>>hi, >>>Hello, AA, >>>how, >>>are, >>>you)
2、双Value类型交互
1、union(otherDataset)并集 、subtract差集、intersection交集、cartesian笛卡尔积
object Demo2{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val RDD1: RDD[Int] = sc.parallelize(1 to 7)
val RDD2: RDD[Int] = sc.makeRDD(6 to 10)
//并集 union 两个rdd数据全部合并 1234567678910
RDD1.union(RDD2).collect().foreach(print)
//差集 只保留rdd1中数据且rdd2没有的数据 41523
RDD1.subtract(RDD2).collect().foreach(print)
//交集 67
RDD1.intersection(RDD2).collect().foreach(print)
//笛卡尔积(尽量不使用) (1,6)(1,7)(1,8)(1,9)(1,10)(2,6)(3,6)(2,7)(3,7)(2,8)(3,8)(2,9)(2,10)...
RDD1.cartesian(RDD2).collect().foreach(print)
}
}
2、zip(otherDataset)案例
作用:将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常
val RDD1: RDD[Int] = sc.parallelize((1 to 5),3)
val RDD2: RDD[String] = sc.parallelize(Array("a","b","c","d","e"),3)
RDD1.zip(RDD2).collect().foreach(print) //(1,a)(2,b)(3,c)(4,d)(5,e)
3、 Key-Value类型
1、partitionBy案例
作用:对pairRDD进行分区操作,如果赋值分区数与之前不一致,会生成ShuffleRDD,即会产生shuffle过程。
可以自定义getpartitioner分区规则,见案例一
val rdd = sc.parallelize(Array((1,"aaa"),(2,"bbb"),(3,"ccc"),(4,"ddd")),4)
println(rdd.partitions.size) //4
val unit: RDD[(Int, String)] = rdd.partitionBy(new org.apache.spark.HashPartitioner(5))
println(unit.partitions.size) //5
2、groupByKey案例
作用:groupByKey也是对每个key进行操作,但只生成一个sequence(所有value的序列)。
val rdd = sc.parallelize(Array("one", "two", "two", "three", "three", "three"))
rdd.map(x=>(x,1)).groupByKey().collect().foreach(println)
/* (two,CompactBuffer(1, 1))
(one,CompactBuffer(1))
(three,CompactBuffer(1, 1, 1)) */
3、reduceByKey(func, [numTasks]) 案例 将相同key的值聚合到一起
//将相同key的值聚合,第二个参数:reduce任务的个数 wcordcount
rdd.map(x=>(x,1)).reduceByKey((x,y)=>x+y,3).collect().foreach(println)
reduceByKey和groupByKey的区别:
reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v].
groupByKey:按照key进行分组,直接进行shuffle。
开发指导:reduceByKey比groupByKey,建议使用。但是需要注意是否会影响业务逻辑。
4、aggregateByKey案例 提供分区内、分区键操作
参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
(1)zeroValue:给每一个分区中的每一个key一个初始值;
(2)seqOp:函数用于在每一个分区中用初始值逐步迭代value; 分区内操作
(3)combOp:函数用于合并每个分区中的结果。 分区间操作
要求:创建一个pairRDD,取出每个分区相同key对应值的最大值,然后相加
object aggregateByKey{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
rdd.glom().collect().foreach(array=>println(array.mkString(",")))
//aggregateByKey()()第一个括号内使进行操作比较的初始值,第二个()内传2个func,分别是分区内,分区外操作
rdd.aggregateByKey(0)(math.max(_,_),_+_).collect().foreach(println)
}
}
5、foldByKey案例
//foldbykey将相同key的value相加
rdd.foldByKey(0)(_+_).collect().foreach(print) //(b,3)(a,5)(c,18)
rdd.foldByKey(5)(_+_).collect().foreach(print) //(b,8)(a,10)(c,28)
6、combineByKey[C] 案例
参数:(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)
(1)createCombiner: 遍历分区中所有元素,如果是一个新元素,调用本函数创建那个键对应的初始值(初始化---->函数)
(2)mergeValue: 如果是已经处理过的键,本方法将当前值与这个新的值进行合并
(3)mergeCombiners: 由于每个分区都是独立处理的, 同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 本 方法将各个分区的结果进行合并。
需求:创建一个pairRDD,根据key计算每种key的均值。(先计算每个key出现的次数以及对应值的总和,再相除得到结果)
//combineByKey 根据key计算每种key的均值
//1.将所有数据改((k1,v1),v2)嵌套元组 2.将 (v1,v2)所有v1相加,v2加一,v是累加器现在的值
//3.将所有分区的v1 v2合并 注意:2.3方法都是(v,v)形式,前v和现v
val combined: RDD[(String, (Int, Int))] = rdd.combineByKey((_, 1), (x: (Int, Int), v) => (x._1 + v, x._2 + 1), (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2))
combined.collect().foreach(print) //(b,(3,1))(a,(5,2))(c,(18,3))
combined.map({case (key,value) => (key,value._1/value._2.toDouble)})
.collect().foreach(print) //(b,3.0)(a,2.5)(c,6.0)
7、mapValues:针对于(K,V)形式的类型只对V进行操作
8、join(otherDataset, [numTasks]) cogroup(otherDataset, [numTasks])
作用:在类型为(K,V)和(K,W)的RDD上调用,
join:返回相同key对应的元素对的(K,(V,W))的RDD cogroup:返回所有key,(K,(Iterable<V>,Iterable<W>))类型的RDD
val rdd3: RDD[(String, Int)] = sc.makeRDD(List(("我们", 1), ("2", 1), ("曾经", 1)))
val rdd4: RDD[(String, Int)] = sc.makeRDD(List(("哈哈", 1), ("我们", 999), ("曾经", 1)))
//只有相同k会组成(K,(V,W))格式
rdd3.join(rdd4).collect().foreach(print) //(曾经,(1,1))(我们,(1,999))
//cogroup返回(K,(Iterable<V>,Iterable<W>)) 所有k都返回,rdd3中独有,rdd4没有的键,会返回空值代替
//(哈哈,(CompactBuffer(),CompactBuffer(1)))(曾经,(CompactBuffer(1),CompactBuffer(1)))
rdd3.cogroup(rdd4).collect().foreach(print)
四、 Action
1、reduce(func)案例
作用:通过func函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据。
val rdd = sc.makeRDD(Array(("a",1),("a",3),("c",3),("d",5)))
//x,y是前后元组
val tuple: (String, Int) = rdd.reduce((x,y)=>{(x._1+y._1,x._2+y._2)})
println(tuple) //(caad,12)
2、collect() : 在驱动程序中,以数组的形式返回数据集的所有元素。
3、count() :返回RDD中元素的个数
4、first(): 返回RDD中的第一个元素
5、take(n)案例:返回一个由RDD的前n个元素组成的数组
rdd.take(2).foreach(array=>print(array.toString())) //(a,1)(a,3)
6、takeOrdered(n): 返回该RDD排序后的前n个元素组成的数组
val rdd2 = sc.makeRDD(Array(("我们",1),("x",3),("1",3),("a",5)))
rdd2.takeOrdered(3).foreach(x=>print(x.toString())) //(1,3)(a,5)(x,3)
7、aggregate (合计)
参数:(zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
作用:aggregate函数将每个分区里面的元素通过seqOp和初始值进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致。
val rdd = sc.parallelize(1 to 10, 2)
//0+1+2+3+4+5=15 0+6+7+8+9+10=40 0-15-40=-55 2个分区,所以0-5 6-10两个操作
val i: Int = rdd.aggregate(0)(_+_,_-_)
print(i) //-55
8、fold(num)(func):折叠操作,aggregate的简化操作,seqop和combop一样。
// 0-1-2-3-4-5=-15 0-6-7-8-9-10=-40 0-(-15)-(-40)=55
val i1: Int = rdd.fold(0)(_-_)
print(i1) //55
9、saveAsTextFile(path):将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统
10、saveAsSequenceFile(path) :将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下
11、saveAsObjectFile(path) :用于将RDD中的元素序列化成对象,存储到文件中。
12、countByKey():针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
countByValue:返回每个kv的个数
val rdd2: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",2),("c",8)))
rdd2.countByKey().foreach(print) //(a,2)(b,1)(c,1)
rdd2.countByValue().foreach(print) // ((c,8),1)((a,2),1)((a,1),1)((b,2),1)
13、foreach(func) 遍历所有元素,一般用于打印rdd.foreach(println(_))
五、案例应用
1、分析log日志,按照职业(域名第一个单词)分组,统计访问次数最多的前三位
数据源:
http://bigdata.edu360.cn/laozhang
http://bigdata.edu360.cn/laozhang
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laoduan
http://javaee.edu360.cn/xiaoxu
http://javaee.edu360.cn/xiaoxu
...........
scala实现代码
object groupTopN{
def main(args: Array[String]): Unit = {
val subject = Array("bigdata","javaee","php")
val conf = new SparkConf().setAppName("myspark2").setMaster("local[*]")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile("in")
//切分数据,形成((job, person), 1)的(嵌套)元组
val value: RDD[((String, String), Int)] = lines.map(x => {
val i: Int = x.lastIndexOf("/")
val person: String = x.substring(i + 1, x.length)
val urlString: String = x.substring(0, i)
val url = new URL(urlString) //url带一个gethost获取域名
val splits: Array[String] = url.getHost.split("\\.") //转义
val job: String = splits(0)
((job, person), 1)
})
val reduced: RDD[((String, String), Int)] = value.reduceByKey(_+_) //_是1+1,2+1,3+1
/* //方法一:groupby方式
val grouped: RDD[(String, Iterable[((String, String), Int)])] = reduced.groupBy(_._1._1)
//mapValues不改变key值,只对value进行操作
// val unit: RDD[(String, List[((String, String), Int)])] = grouped.mapValues(_.toList.sortBy(_._2).reverse.take(3))
val unit: RDD[(String, List[((String, String), Int)])] = grouped.mapValues(x => {
//将value转换成list,调用list的sortby排序,这里是scala的排序,不是spark的
val list: List[((String, String), Int)] = x.toList
val tuples: List[((String, String), Int)] = list.sortBy(_._2).reverse.take(3)
tuples //将数据传递下去
})
unit.collect().foreach(println) */
//方法二:for循环,filter for循环三个职业,filter过滤实现分组
/* for(sb <- subject){
reduced.filter(_._1._1.equals(sb)).sortBy(_._2,false).collect().foreach(println)
} */
//方法三:重写partitonby的方法,使每个职业单独一个分区,分区输出
//sortby会打乱分区,所以先排序,后分区
reduced.sortBy(_._2,false).partitionBy(new myPartition(3))
.glom().collect().foreach(array=>println(array.mkString(",")))
// .saveAsTextFile("out3")
}
}
class myPartition(num :Int) extends Partitioner{
override def numPartitions: Int = num
override def getPartition(key: Any): Int = {
var partValue : Int = key match {
case key if key.toString.contains("bigdata") =>0
case key if key.toString.contains("javaee") =>1
case key if key.toString.contains("php") =>2
}
print("<"+key+","+partValue+">")
partValue
}
}
import java.net.URL
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
import scala.collection.mutable
object MostJobPartitioner {
def main(args: Array[String]): Unit = {
// val topN = args(1).toInt
val topN = 3
val conf = new SparkConf().setAppName("MostJobPartitioner").setMaster("local[4]")
val sc = new SparkContext(conf)
//指定以后从哪里读取数据
val lines: RDD[String] = sc.textFile("C:\\Users\\S\\Desktop\\内民大实训\\person.log")
//整理数据
val jobPersonAndOn: RDD[((String, String), Int)] = lines.map(line => {
val index = line.lastIndexOf("/")
val person = line.substring(index + 1)
val httpHost = line.substring(0, index)
val job = new URL(httpHost).getHost.split("[.]")(0)
((job, person), 1)
})
//计算有多少学科 ((job,person),1)
//jobs()
val jobs: Array[String] = jobPersonAndOn.map(_._1._1).distinct().collect()
//自定义一个分区器,并且按照指定的分区器进行分区
val sbPatitioner = new MyPartitioner(jobs)
//聚合,聚合是就按照指定的分区器进行分区
//该RDD一个分区内仅有一个学科的数据
val reduced: RDD[((String, String), Int)] = jobPersonAndOn.reduceByKey(sbPatitioner, _+_)
//一次拿出一个分区(可以操作一个分区中的数据了)
val sorted: RDD[((String, String), Int)] = reduced.mapPartitions(it => {
//将迭代器转换成list,然后排序,在转换成迭代器返回
//((job,person),3)
it.toList.sortBy(_._2).reverse.take(topN).iterator
//即排序,有不全部加载到内存
//长度为3的一个可以排序的集合
})
//收集结果
val r: Array[((String, String), Int)] = sorted.collect()
println(r.toBuffer)
// sorted.saveAsTextFile("C:\\Users\\S\\Desktop\\内民大实训\\log.log")
sc.stop()
}
}
//自定义分区器 javaee php bigdata
class MyPartitioner(jobs: Array[String]) extends Partitioner {
print("....." + jobs.toBuffer)
//相当于主构造器(new的时候回执行一次)
//用于存放规则的一个map
val rules = new mutable.HashMap[String, Int]()
var i = 0
for(job <- jobs) {
//rules(sb) = i
rules.put(job, i)//rules : (javaee,0) (php,1) (bigdata,2)
i += 1
}
// private val i: Int = rules("javaee")
//返回分区的数量(下一个RDD有多少分区)
override def numPartitions: Int = jobs.length
//根据传入的key计算分区标号
//key是一个元组(String, String)
override def getPartition(key: Any): Int = {
print("....key" + key.toString)
//获取学科名称 (job,person)
val job = key.asInstanceOf[(String, String)]._1
//根据规则计算分区编号
rules(job) //0,1,2
}
}