目錄
一、什麼是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
}
}