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
  }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章