spark中 RDD代碼演示

創建RDD

1.由外部存儲系統的數據集創建,包括本地的文件系統,還有所有Hadoop支持的數據集,比如HDFS、Cassandra、HBase等

val rdd1 = sc.textFile("hdfs://node-01:9000/wordcount/input/words.txt")


2.通過已有的RDD經過算子轉換生成新的RDD

val rdd2=rdd1.flatMap(_.split(" "))


3.由一個已經存在的Scala集合創建

val rdd3 = sc.parallelize(Array(1,2,3,4,5,6,7,8))

或者

val rdd4 = sc.makeRDD(List(1,2,3,4,5,6,7,8))

makeRDD方法底層調用了parallelize方法

spark中 RDD代碼演示

RDD的方法/算子分類

●分類

RDD的算子分為兩類,一類是Transformation轉換操作,一類是Action動作操作

spark中 RDD代碼演示

Transformation轉換操作:返回一個新的RDD


Action動作操作:返回值不是RDD(無返回值或返回其他的)


●注意:

RDD不實際存儲真正要計算的數據,而是記錄了數據的位置在哪裡,RDD的轉換關係(調用了什麼方法,傳入什麼函數)

RDD中的所有轉換都是惰性求值/延遲執行的,也就是說並不會直接計算。

只有當發生一個要求返回結果給Driver的Action動作時,這些轉換才會真正運行。

之所以使用惰性求值/延遲執行,是因為這樣可以在Action時對RDD操作形成的DAG有向無環圖進行Stage的劃分和並行優化

這種設計讓Spark更加有效率地運行。


●總結:如何區分

返回值是RDD的為Transformation轉換操作

返回值不是RDD(如Unit、Array)的為Action動作操作


Transformation算子(轉換)


spark中 RDD代碼演示


Action算子(行動/動作)


spark中 RDD代碼演示


統計操作(屬於Action)


spark中 RDD代碼演示


基礎練習[快速演示]

1.準備工作

●集群模式啟動


啟動Spark集群

/opt/soft/spark/sbin/start-all.sh


啟動spark-shell

/opt/soft/spark/bin/spark-shell \

--master spark://node-01:7077,node-02:7077 \

--executor-memory 1g \

--total-executor-cores 2


●或本地模式啟動

/opt/soft/spark/bin/spark-shell


2.API演示

2.1 WordCount

val res = sc.textFile("hdfs://node-01:9000/wordcount/input/words.txt").flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_)


//上面的代碼不會立即執行,因為都是Transformation轉換操作

//下面的代碼才會真正的提交併執行,因為是Action動作/行動操作

res.collect


2.2創建RDD

val rdd1 = sc.parallelize(List(5,6,4,7,3,8,2,9,1,10))
val rdd2 = sc.makeRDD(List(5,6,4,7,3,8,2,9,1,10))


2.3查看該RDD的分區數量

sc.parallelize(List(5,6,4,7,3,8,2,9,1,10)).partitions.length //沒有指定分區數,默認值是2
sc.parallelize(List(5,6,4,7,3,8,2,9,1,10),3).partitions.length //指定了分區數為3


sc.textFile("hdfs://node-01:9000/wordcount/input/words.txt").partitions.length //2


Hadoop的分區:目的併發讀寫

hive的分區、分桶:hive分區就是分文件夾,目的是提高查詢效率,hive的分桶就是分文件,目的提高查詢效率

Kafak的分區:目的併發讀寫

HBase的分區(Region):數據會按照Rowkey的範圍放到不同的Region,目的也是提高效率

注意:HBase寫比讀快,底層是LSM樹決定的


Spark的分區:目的是為了並行計算,要充分利用CPU的Core計算資源


●RDD分區的數據取決於哪些因素?

RDD分區的原則是使得分區的個數儘量等於集群中的CPU核心(core)數目,這樣可以充分利用CPU的計算資源

RDD分區數和啟動時指定的核數、調用方法時指定的分區數、如文件本身分區數 有關係


●擴展:

1.啟動的時候指定的CPU核數確定了一個參數值:

spark.default.parallelism=指定的CPU核數(集群模式最小2)

2.對於Scala集合調用parallelize(集合,分區數)方法,

如果沒有指定分區數,就使用spark.default.parallelism,

如果指定了就使用指定的分區數(不要指定大於spark.default.parallelism)

3.對於textFile(文件,分區數)

defaultMinPartitions

如果沒有指定分區數sc.defaultMinPartitions=min(defaultParallelism,2)

如果指定了就使用指定的分區數sc.defaultMinPartitions=指定的分區數

rdd的分區數

對於本地文件:rdd的分區數 = max(本地file的分片數, sc.defaultMinPartitions)

對於HDFS文件:rdd的分區數 = max(hdfs文件的block數目, sc.defaultMinPartitions)

所以如果分配的核數為多個,且從文件中讀取數據創建RDD,即使hdfs文件只有1個切片,最後的Spark的RDD的partition數也有可能是2


2.4 map

對RDD中的每一個元素進行操作並返回操作的結果

//通過並行化生成rdd

val rdd1 = sc.parallelize(List(5, 6, 4, 7, 3, 8, 2, 9, 1, 10))

//對rdd1裡的每一個元素

rdd1.map(_ * 2).collect //collect方法表示收集,是action操作


2.5 filter

對RDD中的每一個元素進行過濾,返回true留下,false過濾掉

val rdd2 = sc.parallelize(List(5, 6, 4, 7, 3, 8, 2, 9, 1, 10))

//過濾出大於等於10的元素

val rdd3 = rdd2.filter(_ >= 10)

rdd3.collect


2.6 flatmap

對RDD中的每一個元素進行先map再壓扁,最後返回操作的結果

val rdd1 = sc.parallelize(Array("a b c", "d e f", "h i j"))

//將rdd1裡面的每一個元素先切分再壓平

val rdd2 = rdd1.flatMap(_.split(' '))

rdd2.collect

//Array[String] = Array(a, b, c, d, e, f, h, i, j)


2.7 sortBy

val rdd1 = sc.parallelize(List(5, 6, 4, 7, 3, 8, 2, 9, 1, 10))

val rdd2 = rdd1.sortBy(x=>x,true) //默認升序

rdd2.collect

val rdd2 = rdd1.sortBy(x=>x+"",true)//變成了字符串,結果為字典順序

rdd2.collect


2.8 交集、並集、差集、笛卡爾積

注意類型要一致

val rdd1 = sc.parallelize(List(5, 6, 4, 3))

val rdd2 = sc.parallelize(List(1, 2, 3, 4))

//求並集不會去重

val rdd3 = rdd1.union(rdd2)

rdd3.collect

//去重

rdd3.distinct.collect

//求交集

val rdd4 = rdd1.intersection(rdd2)

rdd4.collect

//求差集

val rdd5 = rdd1.subtract(rdd2)

rdd5.collect

//笛卡爾積

val rdd1 = sc.parallelize(List("jack", "tom"))//學生

val rdd2 = sc.parallelize(List("java", "python", "scala"))//課程

val rdd3 = rdd1.cartesian(rdd2)//結果就是所有學生可能的選課情況

rdd3.collect

//Array[(String, String)] = Array((jack,java), (jack,python), (jack,scala), (tom,java), (tom,python), (tom,scala))


2.9 join

join(內連接)聚合具有相同key組成的value元組

val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 2), ("kitty", 3)))

val rdd2 = sc.parallelize(List(("jerry", 9), ("tom", 8), ("shuke", 7), ("tom", 2)))

val rdd3 = rdd1.join(rdd2)

rdd3.collect

//Array[(String, (Int, Int))] = Array((tom,(1,8)), (tom,(1,2)), (jerry,(2,9)))

●圖解

spark中 RDD代碼演示

val rdd4 = rdd1.leftOuterJoin(rdd2) //左外連接,左邊的全留下,右邊的滿足條件的才留下

rdd4.collect

//Array[(String, (Int, Option[Int]))] = Array((tom,(1,Some(2))), (tom,(1,Some(8))), (jerry,(2,Some(9))), (kitty,(3,None)))

●圖解

spark中 RDD代碼演示

val rdd5 = rdd1.rightOuterJoin(rdd2)

rdd5.collect

//Array[(String, (Option[Int], Int))] = Array((tom,(Some(1),2)), (tom,(Some(1),8)), (jerry,(Some(2),9)), (shuke,(None,7)))


val rdd6 = rdd1.union(rdd2)

rdd6.collect

//Array[(String, Int)] = Array((tom,1), (jerry,2), (kitty,3), (jerry,9), (tom,8), (shuke,7), (tom,2))


2.10 groupbykey

groupByKey()的功能是,對具有相同鍵的值進行分組。

比如,對四個鍵值對("spark",1)、("spark",2)、("hadoop",3)和("hadoop",5),

採用groupByKey()後得到的結果是:("spark",(1,2))和("hadoop",(3,5))。


//按key進行分組

val rdd6 = sc.parallelize(Array(("tom",1), ("jerry",2), ("kitty",3), ("jerry",9), ("tom",8), ("shuke",7), ("tom",2)))

val rdd7=rdd6.groupByKey

rdd7.collect

//Array[(String, Iterable[Int])] = Array((tom,CompactBuffer(1, 8, 2)), (jerry,CompactBuffer(2, 9)), (shuke,CompactBuffer(7)), (kitty,CompactBuffer(3)))


2.11 cogroup(瞭解)

先在RDD內部按照key分組,再在多個RDD間按照key分組

val rdd1 = sc.parallelize(List(("tom", 1), ("tom", 2), ("jerry", 3), ("kitty", 2)))

val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 1), ("shuke", 2)))

val rdd3 = rdd1.cogroup(rdd2)

rdd3.collect

//Array[(String, (Iterable[Int], Iterable[Int]))] = Array((tom,(CompactBuffer(1, 2),CompactBuffer(1))), (jerry,(CompactBuffer(3),CompactBuffer(2))), (shuke,(CompactBuffer(),CompactBuffer(2))), (kitty,(CompactBuffer(2),CompactBuffer())))


2.12 groupBy(瞭解)

根據指定的函數中的規則/key進行分組

val intRdd = sc.parallelize(List(1,2,3,4,5,6))

val result = intRdd.groupBy(x=>{if(x%2 == 0)"even" else "odd"}).collect

//Array[(String, Iterable[Int])] = Array((even,CompactBuffer(2, 4, 6)), (odd,CompactBuffer(1, 3, 5)))


2.13 reduce--注意是Action算子

val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5))

//reduce聚合

val result = rdd1.reduce(_ + _) //第一_ 上次一個運算的結果,第二個_ 這一次進來的元素


2.14 reducebykey--轉換算子

reduceByKey(func)的功能是,使用func函數合併具有相同鍵的值。

比如,reduceByKey((a,b) => a+b),有四個鍵值對("spark",1)、("spark",2)、("hadoop",3)和("hadoop",5),

對具有相同key的鍵值對進行合併後的結果就是:("spark",3)、("hadoop",8)。

可以看出,(a,b) => a+b這個Lamda表達式中,a和b都是指value,

比如,對於兩個具有相同key的鍵值對("spark",1)、("spark",2),a就是1,b就是2。


val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2), ("shuke", 1)))

val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 3), ("shuke", 2), ("kitty", 5)))

val rdd3 = rdd1.union(rdd2) //並集

rdd3.collect

//Array[(String, Int)] = Array((tom,1), (jerry,3), (kitty,2), (shuke,1), (jerry,2), (tom,3), (shuke,2), (kitty,5))

//按key進行聚合

val rdd4 = rdd3.reduceByKey(_ + _)

rdd4.collect

//Array[(String, Int)] = Array((tom,4), (jerry,5), (shuke,3), (kitty,7))


2.15 repartition

改變分區數

val rdd1 = sc.parallelize(1 to 10,3) //指定3個分區

//利用repartition改變rdd1分區數

//減少分區

rdd1.repartition(2).partitions.length //新生成的rdd分區數為2

rdd1.partitions.length //3 //注意:原來的rdd分區數不變

//增加分區

rdd1.repartition(4).partitions.length

//減少分區

rdd1.repartition(3).partitions.length

//利用coalesce改變rdd1分區數

//減少分區

rdd1.coalesce(2).partitions.size


注意:

repartition可以增加和減少rdd中的分區數,

coalesce默認減少rdd分區數,增加rdd分區數不會生效。

不管增加還是減少分區數原rdd分區數不變


應用場景:

在把處理結果保存到hdfs上之前可以減少分區數(合併小文件)


2.16 collect

val rdd1 = sc.parallelize(List(6,1,2,3,4,5), 2)

rdd1.collect


2.17 count

求RDD中最外層元素的個數

rdd1.count


val rdd3 = sc.parallelize(List(List("a b c", "a b b"),List("e f g", "a f g"), List("h i j", "a a b")))


rdd3.count //3


2.18 distinct

val rdd = sc.parallelize(Array(1,2,3,4,5,5,6,7,8,1,2,3,4), 3)

rdd.distinct.collect


2.19 top

//取出最大的前N個

val rdd1 = sc.parallelize(List(3,6,1,2,4,5))

rdd1.top(2)


2.20 take

//按照原來的順序取前N個

rdd1.take(2)

//需求:取出最小的2個

rdd1.sortBy(x=>x,true).take(2)


2.21 first

//按照原來的順序取前第一個

rdd1.first

2.22 keys、values

val rdd1 = sc.parallelize(List("dog", "tiger", "lion", "cat", "panther", "eagle"), 2)

val rdd2 = rdd1.map(x => (x.length, x))

rdd2.collect

//Array[(Int, String)] = Array((3,dog), (5,tiger), (4,lion), (3,cat), (7,panther), (5,eagle))

rdd2.keys.collect

//Array[Int] = Array(3, 5, 4, 3, 7, 5)

rdd2.values.collect

//Array[String] = Array(dog, tiger, lion, cat, panther, eagle)


2.23 mapValues

val rdd1 = sc.parallelize(List((1,10),(2,20),(3,30)))

val rdd2 = rdd1.mapValues(_*2).collect //_表示每一個value ,key不變,將函數作用於value

//Array[(Int, Int)] = Array((1,20), (2,40), (3,60))


2.24 collectAsMap

val rdd = sc.parallelize(List(("a", 1), ("b", 2)))

rdd.collectAsMap

//scala.collection.Map[String,Int] = Map(b -> 2, a -> 1)


2.25 面試題:foreach和foreachPartition

val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9), 3)

rdd1.foreach(x => println(x*100)) //把函數傳給各個分區,在分區內循環遍歷該分區中的元素 //x每個元素,即一個一個的數字

rdd1.foreachPartition(x => println(x.reduce(_ + _))) //把各個分區傳遞給函數執行 //x是每個分區


應用場景:

比如在函數中要將RDD中的元素保存到數據庫

foreach:會將函數作用到RDD中的每一條數據,那麼有多少條數據,操作數據庫連接的開啟關閉就得執行多少次

foreachPartition:將函數作用到每一個分區,那麼每一個分區執行一次數據庫連接的開啟關閉,有幾個分區就會執行數據庫連接開啟關閉


2.26 面試題:map和mapPartitions

將每一個分區傳遞給函數

val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9), 3)

rdd1.mapPartitions(x=>x.map(y=>y*2)).collect //x是每個分區,y是分區中的每個元素


2.27 擴展:mapPartitionsWithIndex

功能:取分區中對應的數據時,還可以將分區的編號取出來,這樣就可以知道數據是屬於哪個分區的(哪個區分對應的Task的數據)

val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)

//該函數的功能是將對應分區中的數據取出來,並且帶上分區編號

val func = (index: Int, iter: Iterator[Int]) => {

iter.map(x => "[partID:" + index + ", val: " + x + "]")

}

rdd1.mapPartitionsWithIndex(func).collect

//Array[String] = Array(

[partID:0, val: 1], [partID:0, val: 2], [partID:0, val: 3], [partID:0, val: 4],

[partID:1, val: 5], [partID:1, val: 6], [partID:1, val: 7], [partID:1, val: 8], [partID:1, val: 9]

)

2.28 擴展:aggregate

聚合

//0表示初始值,每個分區會有初始值,彙總的時候也有初始值

//第一個函數:表示各個分區如何操作

//第二個函數:表示對分區如何彙總操作

val result1: Int = rdd1.aggregate(0)(_+_,_+_) //45

val result2: Int = rdd1.aggregate(10)(_+_,_+_) //75

println(result1)

println(result2)


//源碼中的註釋:

@param zerovalue每個分區的累積結果的初始值

*`seqop`運算符,以及合併結果的初始值

*"combop"運算符的不同分區-這通常是中性元素(例如,"nil"用於列表連接,或"0"用於求和)

*@param seqop用於在分區內累積結果的運算符

*@param combop用於組合來自不同分區的結果的關聯運算符


2.29 擴展:aggregateByKey

val pairRDD = sc.parallelize(List( ("cat",2), ("cat", 5), ("mouse", 4),("cat", 12), ("dog", 12), ("mouse", 2)), 2)

def func2(index: Int, iter: Iterator[(String, Int)]) : Iterator[String] = {

iter.map(x => "[partID:" + index + ", val: " + x + "]")

}

pairRDD.mapPartitionsWithIndex(func2).collect

//Array[String] = Array(

[partID:0, val: (cat,2)], [partID:0, val: (cat,5)], [partID:0, val: (mouse,4)],

[partID:1, val: (cat,12)], [partID:1, val: (dog,12)], [partID:1, val: (mouse,2)]

)

pairRDD.aggregateByKey(0)(math.max(_, _), _ + _).collect

// Array[(String, Int)] = Array((dog,12), (cat,17), (mouse,6))

pairRDD.aggregateByKey(100)(math.max(_, _), _ + _).collect


2.30 擴展:combineByKey

val rdd1 = sc.textFile("hdfs://node-01:9000/wordcount/input/words.txt").flatMap(_.split(" ")).map((_, 1))

//Array((hello,1), (me,1), (hello,1), (you,1), (hello,1), (her,1))

val rdd2 = rdd1.combineByKey(x => x, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n)

//val rdd2 = rdd1.combineByKey(_, _+_, _+_) //錯誤! 能省則省,不能省就不要偷懶了


rdd2.collect

//Array[(String, Int)] = Array((hello,3), (me,1), (you,1), (her,1))


https://blog.csdn.net/high2011/article/details/78852182


小練習

●需求

給定一個鍵值對RDD

val rdd = sc.parallelize(Array(("spark",2),("hadoop",6),("hadoop",4),("spark",6)))

key表示圖書名稱,

value表示某天圖書銷量,

請計算每個鍵對應的平均值,也就是計算每種圖書的每天平均銷量。最終結果:("spark",4),("hadoop",5)


●答案

val rdd = sc.parallelize(Array(("spark",2),("hadoop",6),("hadoop",4),("spark",6)))

val rdd2 = rdd.groupByKey()

rdd2.collect

//Array[(String, Iterable[Int])] = Array((spark,CompactBuffer(2, 6)), (hadoop,CompactBuffer(6, 4)))

val rdd3 = rdd2.map(t=>(t._1,t._2.sum /t._2.size))

rdd3.collect

//Array[(String, Int)] = Array((spark,4), (hadoop,5))


寫成一行

sc.parallelize(Array(("spark",2),("hadoop",6),("hadoop",4),("spark",6)))
.groupByKey().map(t=>(t._1,t._2.sum / t._2.size)).collect


總結

●分類

RDD的算子分為兩類,一類是Transformation轉換操作,一類是Action動作操作


●總結:如何區分Transformation和Action

返回值是RDD的為Transformation轉換操作,延遲執行/懶執行/惰性執行

返回值不是RDD(如Unit、Array、Int)的為Action動作操作


●面試題:

1.Transformation操作的API有哪些? --map/flatMap/filter....

2.Action操作的API有哪些? --collect/reduce/saveAsTextFile....

3.reduceByKey是Transformation還是Action? --Transformation

4.reduce是Transformation還是Action? -- Action


●注意:

RDD不實際存儲真正要計算的數據,而只是記錄了RDD的轉換關係(調用了什麼方法,傳入什麼函數,依賴哪些RDD,分區器是什麼,數量塊來源機器列表)

RDD中的所有轉換操作都是延遲執行(懶執行)的,也就是說並不會直接計算。

只有當發生Action操作的時候,這些轉換才會真正運行。


分享到:


相關文章: