SparkCore 笔记(一)

目录

 

一、什么是RDD

二、RDD编程模型

1、RDD三种创建方式

三、RDD的转换(面试开发重点)

1、value类型

2、双Value类型交互

3、 Key-Value类型

四、 Action

五、案例应用


一、什么是RDD

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集(弹性:内存不足,自动写入磁盘,是Spark中最基本的数据(计算)抽象(抽象:不存数据)。代码中是一个抽象类,它代表一个不可变、可分区、里面的元素可并行计算的集合。

RDD五大特性

  1. RDD是由一系列的partition组成的。
  2. 函数是作用在每一个partition(split)上的。
  3. RDD之间有一系列的依赖关系。
  4. 分区器是作用在K,V格式的RDD上。
  5. 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)

1zeroValue给每一个分区中的每一个key一个初始值;

2seqOp函数用于在每一个分区中用初始值逐步迭代value;   分区内操作

3combOp函数用于合并每个分区中的结果。                           分区间操作

要求:创建一个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)  

1createCombiner: 遍历分区中所有元素,如果是一个新元素,调用本函数创建那个键对应的初始值(初始化---->函数)

2mergeValue: 如果是已经处理过的键,本方法将当前值与这个新的值进行合并

3mergeCombiners: 由于每个分区都是独立处理的, 同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 本 方法将各个分区的结果进行合并。

需求:创建一个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
  }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值