Spark中的Transformations和Actions介紹

RDD提供了兩種類型的操作:transformationaction

  1. 所有的transformation都是採用的懶策略,如果只是將transformation提交是不會執行計算的,計算只有在action被提交的時候才被觸發。
  2. action操作:action是得到一個值,或者一個結果(直接將RDD cache到內存中)

常見的轉換操作有

Transformation 算子 含義
map(func) 對原 RDD 中每個元素運用 func 函數,並生成新的 RDD
filter(func) 對原 RDD 中每個元素使用func 函數進行過濾,並生成新的 RDD
flatMap(func) 與 map 類似,但是每一個輸入的 item 被映射成 0 個或多個輸出的 items( func 返回類型需要爲 Seq )
mapPartitions(func) 與 map 類似,但函數單獨在 RDD 的每個分區上運行, func函數的類型爲 Iterator<T> => Iterator<U> ,其中 T 是 RDD 的類型,即 RDD[T]
mapPartitionsWithIndex(func) 與 mapPartitions 類似,但 func 類型爲 (Int, Iterator<T>) => Iterator<U> ,其中第一個參數爲分區索引
sample(withReplacement, fraction, seed) 數據採樣,有三個可選參數:設置是否放回(withReplacement)、採樣的百分比(fraction)、隨機數生成器的種子(seed);
union(otherDataset) 合併兩個 RDD
intersection(otherDataset) 求兩個 RDD 的交集
distinct([numPartitions])) 去重
groupByKey([numPartitions]) 按照 key 值進行分區,即在一個 (K, V) 對的 dataset 上調用時,返回一個 (K, Iterable<V>)
Note: 如果分組是爲了在每一個 key 上執行聚合操作(例如,sum 或 average),此時使用 reduceByKeyaggregateByKey 性能會更好
Note: 默認情況下,並行度取決於父 RDD 的分區數。可以傳入 numTasks 參數進行修改。
reduceByKey(func, [numPartitions]) 按照 key 值進行分組,並對分組後的數據執行歸約操作。
aggregateByKey(zeroValue)(seqOp, combOp, [numPartitions]) 當調用(K,V)對的數據集時,返回(K,U)對的數據集,其中使用給定的組合函數和 zeroValue 聚合每個鍵的值。與 groupByKey 類似,reduce 任務的數量可通過第二個參數進行配置。
sortByKey([ascending], [numPartitions]) 按照 key 進行排序,其中的 key 需要實現 Ordered 特質,即可比較
join(otherDataset, [numPartitions]) 在一個 (K, V) 和 (K, W) 類型的 dataset 上調用時,返回一個 (K, (V, W)) pairs 的 dataset,等價於內連接操作。如果想要執行外連接,可以使用 leftOuterJoin, rightOuterJoinfullOuterJoin 等算子。
cogroup(otherDataset, [numPartitions]) 在一個 (K, V) 對的 dataset 上調用時,返回一個 (K, (Iterable<V>, Iterable<W>)) tuples 的 dataset。
cartesian(otherDataset) 在一個 T 和 U 類型的 dataset 上調用時,返回一個 (T, U) 類型的 dataset(即笛卡爾積)。
coalesce(numPartitions) 將 RDD 中的分區數減少爲 numPartitions。
repartition(numPartitions) 隨機重新調整 RDD 中的數據以創建更多或更少的分區,並在它們之間進行平衡。
action算子 含義
reduce(func) (func)使用函數func執行歸約操作
collect() 以一個 array 數組的形式返回 dataset 的所有元素,適用於小結果集。
count() 返回 dataset 中元素的個數。
first() 返回 dataset 中的第一個元素,等價於 take(1)。
take(n) 將數據集中的前 n 個元素作爲一個 array 數組返回。
takeSample(withReplacement, num, [seed]) 對一個 dataset 進行隨機抽樣**
takeOrdered**(n, [ordering]) 按自然順序(natural order)或自定義比較器(custom comparator)排序後返回前 n 個元素。只適用於小結果集,因爲所有數據都會被加載到驅動程序的內存中進行排序。
saveAsTextFile(path) 將 dataset 中的元素以文本文件的形式寫入本地文件系統、HDFS 或其它 Hadoop 支持的文件系統中。Spark 將對每個元素調用 toString 方法,將元素轉換爲文本文件中的一行記錄。
saveAsSequenceFile(path) 將 dataset 中的元素以 Hadoop SequenceFile 的形式寫入到本地文件系統、HDFS 或其它 Hadoop 支持的文件系統中。該操作要求 RDD 中的元素需要實現 Hadoop 的 Writable 接口。對於 Scala 語言而言,它可以將 Spark 中的基本數據類型自動隱式轉換爲對應 Writable 類型。(目前僅支持 Java and Scala)
saveAsObjectFile(path) 使用 Java 序列化後存儲,可以使用 SparkContext.objectFile() 進行加載。(目前僅支持 Java and Scala)**
countByKey**() 計算每個鍵出現的次數。
foreach(func) 遍歷 RDD 中每個元素,並對其執行fun函數

Transformations

  1. map(func) 算子
 var listRDD = sc.makeRDD(1 to 10)
 listRDD.map((_*2)).foreach(println)
// 輸出結果: 2,4,6,8,10,12,14,16,18,20 (這裏爲了節省篇幅去掉了換行,後文亦同)
  1. filter(func)算子
var listRDD = sc.makeRDD(1 to 10)
listRDD .filter(_%2==0).foreach(println)  
// 輸出結果: 2,4,6,8,10(這裏爲了節省篇幅去掉了換行,後文亦同)
  1. flatMap(func)
    flatMap(func)map類似,但每一個輸入的 item 會被映射成 0 個或多個輸出的 items( *func* 返回類型需要爲Seq`)
val list = List(List(1, 2), List(3), List(), List(4, 5))
sc.parallelize(list).flatMap(_.toList).map(_ * 10).foreach(println)
// 輸出結果: 10,20,30,40,50(這裏爲了節省篇幅去掉了換行,後文亦同)

flatmap 流的扁平化,最終輸出的數據類型爲一維數組Array[String]
被分割出來的每個數據都作爲同一個數組中的相同類型的元素,最終全部被分割出來的數據都存儲於同一個一維數組中。
結論:多行數據被分割後都存儲到同一個一維數組Array[String]中。
flatMap 這個算子在日誌分析中使用概率非常高,這裏進行一下演示:拆分輸入的每行數據爲單個單詞,並賦值爲 1,代表出現一次,之後按照單詞分組並統計其出現總次數,代碼如下:

var list = List("hello scala","hello python")
sc.parallelize(list).flatMap(line=>line.split(" ")).map((_,1)).reduceByKey(_+_).foreach(println)
// 輸出結果:(scala,1)(python,1)(hello,2)(這裏爲了節省篇幅去掉了換行,後文亦同)
  1. mapPartitions(func)
    mapPartitions算子,與 map 類似,但函數單獨在 RDD 的每個分區上運行(map函數作用在每一條數據上), func函數的類型爲 Iterator<T> => Iterator<U> (其中 T 是 RDD 的類型),即輸入和輸出都必須是可迭代類型。
 val listRdd: RDD[Int] = sc.makeRDD(1 to 10)
    //mappartition對一個rdd中所有的分區進行遍歷
    //優於map算子,減少發到執行器的交互次數
    //可能內存溢出
    val partitions: RDD[Int] = listRdd.mapPartitions(data=>{data.map(data=>data*2)})
//輸出結果:2,4,6,8,10,12,1,16,18,20
  1. mapPartitionsWithIndex(func)
    與 mapPartitions 類似,但 func 類型爲 (Int, Iterator<T>) => Iterator<U> ,其中第一個參數爲分區索引。
  val listRdd: RDD[Int] = sc.makeRDD(1 to 10,2)
    val tupRDD: RDD[(Int, String)] = listRdd.mapPartitionsWithIndex {
      case (num, datas) => {
        datas.map((_, "分區號:" + num))
      }
    }
//輸出結果(1,分區號:0)(2,分區號:0)(3,分區號:0)(4,分區號:0)(5,分區號:0)(6,分區號:1,(7,分區號:1)(8,分區號:1)(9,分區號:1)(10,分區號:1)
  1. sample(withReplacement, fraction, seed)
    以指定的隨機種子隨機抽樣出數量爲fraction的數據,withReplacement表示是抽出的數據是否放回,true爲有放回的抽樣,false爲無放回的抽樣,seed用於指定隨機數生成器種子。
 val rdd: RDD[String] = sc.makeRDD(Array("hello1","hello1","hello2","hello3","hello4","hello5","hello6","hello1","hello1","hello2","hello3"))
    val sampleRDD: RDD[String] = rdd.sample(false,0.7)
    sampleRDD.foreach(println)
//輸出結果hello1 hello6 hello1 hello2 hello3 hello5 hello1 hello3
  1. union(otherDataset)
    對源RDD和參數RDD求並集後返回一個新的RDD
	val rdd1 = sc.parallelize(1 to 5)
    val rdd2 = sc.parallelize(5 to 10)
    val rdd3 = rdd1.union(rdd2)
    rdd3.collect().foreach(println)
//輸出結果1 2 3 4 5 5 6 7 8 9 10
  1. intersection(otherDataset)
    對源RDD和參數RDD求交集後返回一個新的RDD
val rdd6 = sc.parallelize(1 to 7)
val rdd7 = sc.parallelize(5 to 10)
val rdd8 = rdd6.intersection(rdd7)
rdd8.collect().foreach(println)
//輸出結果5 6 7
  1. distinct([numPartitions]))
    對源RDD進行去重後返回一個新的RDD。默認情況下,只有8個並行任務來操作,但是可以傳入一個可選的numTasks參數改變它。
val makeRDD: RDD[Int] = sc.makeRDD(Array(1,3,2,5,6,3,2,1))
val makedist: RDD[Int] = makeRDD.distinct() //不指定並行度
/**對RDD(指定並行度爲2)
 makeRDD.distinct(2)*/
makedist.collect().foreach(println)
//輸出結果1 2 3 5 6
  1. groupByKey([numPartitions])
    groupByKey也是對每個key進行操作,但只生成一個sequence
 	val words = Array("one","two","three","one","two","three")
    val listRdd: RDD[(String, Int)] = sc.makeRDD(words).map(word => (word,1))
    listRdd.collect().foreach(println)
    //groupByKey算子
    val group: RDD[(String, Iterable[Int])] = listRdd.groupByKey()
    group.collect().foreach(println)
//輸出結果(two,CompactBuffer(1, 1)) (one,CompactBuffer(1, 1)) (three,CompactBuffer(1, 1))
  1. reduceByKey(func, [numPartitions])
    在一個(K,V)的RDD上調用,返回一個(K,V)的RDD,使用指定的reduce函數,將相同key的值聚合到一起,reduce任務的個數可以通過第二個可選的參數來設置。
 val words = Array("one","two","three","one","two","three")
 val listRdd: RDD[(String, Int)] = sc.makeRDD(words).map(word => (word,1))
 val reduceByKeyRDD: Array[(String, Int)] = listRdd.reduceByKey(_+_).collect()
//輸出結果(two,2) (one,2) (three,2)
  1. aggregateByKey(zeroValue)(seqOp, combOp, [numPartitions])
    參數描述:
    (1)zeroValue:給每一個分區中的每一個key一個初始值;
    (2)seqOp:函數用於在每一個分區中用初始值逐步迭代value;
    (3)combOp:函數用於合併每個分區中的結果
    kv對的RDD中,,按keyvalue進行分組合並,合併時,將每個value和初始值作爲seq函數的參數,進行計算,返回的結果作爲一個新的kv對,然後再將結果按照key進行合併,最後將每個分組的value傳遞給combine函數進行計算(先將前兩個value進行計算,將返回結果和下一個value傳給combine函數,以此類推),將key與計算結果作爲一個新的kv對輸出。
val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
    val aggregateByKeyRDD: RDD[(String, Int)] = rdd.aggregateByKey(0)(math.max(_,_),_+_)
    aggregateByKeyRDD.collect().foreach(println)
//輸出結果(b,3), (a,3), (c,12)

在這裏插入圖片描述

  1. sortByKey([ascending], [numPartitions])
    在一個(K,V)的RDD上調用,K必須實現Ordered接口,返回一個按照key進行排序的(K,V)的RDD
	val rdd = sc.parallelize(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))
    //根據key正序排
    rdd.sortByKey(true).collect().foreach(println)
    //倒序排序
    rdd.sortByKey(false).collect().foreach(println)
//輸出結果(1,dd)(2,bb)(3,aa)(6,cc)(6,cc) (3,aa) (2,bb) (1,dd)
  1. join(otherDataset, [numPartitions])
    在類型爲(K,V)和(K,W)的RDD上調用,返回一個相同key對應的所有元素對在一起的(K,(V,W))的RDD,等價於內連接操作。如果想要執行外連接,可以使用 leftOuterJoin, rightOuterJoinfullOuterJoin 等算子。
 val rdd = sc.parallelize(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))
 val rdd1 = sc.parallelize(Array((1,4),(2,5),(3,6)))
  rdd.join(rdd1).collect().foreach(println)
//輸出結果(1,(dd,4)) (2,(bb,5)) (3,(aa,6))
  1. cogroup(otherDataset, [numPartitions])
    在類型爲(K,V)和(K,W)的RDD上調用,返回一個(K,(Iterable<V>,Iterable<W>))類型的RDD
 val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
    val rdd1 = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
    rdd.cogroup(rdd1).collect().foreach(println)
//輸出結果(1,(CompactBuffer(a),CompactBuffer(a))) (2,(CompactBuffer(b),CompactBuffer(b))) (3,(CompactBuffer(c),CompactBuffer(c)))
  1. cartesian(otherDataset)
    計算笛卡爾積
val list1 = List("A", "B", "C")
val list2 = List(1, 2, 3)
sc.parallelize(list1).cartesian(sc.parallelize(list2)).foreach(println)
//輸出笛卡爾積(A,1)(A,2)(A,3)(B,1)(B,2)(B,3)(C,1)(C,2)(C,3)
  1. coalesce(numPartitions)
    coalesce重新分區,可以選擇是否進行shuffle過程。由參數shuffle: Boolean = false/true決定。
 val makeRdd: RDD[Int] = sc.makeRDD(1 to 16,4)
    //縮減分區數,可以理解爲合併分區,第三個和第四個的數據分區合併了
    val coalesceRdd: RDD[Int] = makeRdd.coalesce(3)
    coalesceRdd.saveAsTextFile("output")
    println(coalesceRdd.partitions.size)
//輸出結果:3
  1. repartition(numPartitions)
    根據分區數,重新通過網絡隨機洗牌所有數據。
 val parallelize: RDD[Int] = sc.parallelize(1 to 16,4)
    println(parallelize.partitions.size)
    val rerdd = parallelize.repartition(2)
    println(rerdd.partitions.size)
    val glom: RDD[Array[Int]] = rerdd.glom()
    glom.collect().foreach(array=>{
      println(array.mkString(","))
    })
輸出結果1,3,5,7,9,11,13,15        2,4,6,8,10,12,14,16

Actions

  1. reduce(func)
    使用函數func執行歸約操作:
val list = List(1, 2, 3, 4, 5)
sc.parallelize(list).reduce((x, y) => x + y)
sc.parallelize(list).reduce(_ + _)
// 輸出結果 15
  1. collect()
    在驅動程序中,以數組的形式返回數據集的所有元素。
val rdd = sc.parallelize(1 to 10)
    println(rdd.collect.mkString(","))
//輸出結果 1,2,3,4,5,6,7,8,9,10
  1. count()
    返回RDD中元素的個數
  val rdd = sc.parallelize(1 to 10)
    println(rdd.count)
輸出結果 10
  1. first()
    返回RDD中的第一個元素
 val rdd = sc.parallelize(1 to 10)
    println(rdd.first)
輸出結果 1
  1. take(n)
    返回一個由RDD的前n個元素組成的數組
 val rdd = sc.parallelize(Array(2,5,4,6,8,3))
    println(rdd.take(3).mkString(","))
輸出結果 2 5 4
  1. takeOrdered(n)
    返回該RDD排序後的前n個元素組成的數組
val makeRDD: RDD[Int] = sc.makeRDD(Array(2,5,4,6,3,8))
    val ordered: Array[Int] = makeRDD.takeOrdered(3)
    ordered.foreach(println)
輸出結果 2 3 4
  1. saveAsTextFile
    dataset 中的元素以文本文件的形式寫入本地文件系統、HDFS 或其它 Hadoop 支持的文件系統中。Spark 將對每個元素調用 toString 方法,將元素轉換爲文本文件中的一行記錄。
val list = List(("hadoop", 10), ("hadoop", 10), ("storm", 3), ("storm", 3), ("azkaban", 1))
sc.parallelize(list).saveAsTextFile("/usr/file/temp")
  1. countByKey()
    針對(K,V)類型的RDD,返回一個(K,Int)的map,表示每一個key對應的元素個數。
 val rdd = sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)),3)
    println(rdd.countByKey)
輸出結果 Map(3 -> 2, 1 -> 3, 2 -> 1)
  1. foreach(func)
    在數據集的每一個元素上,運行函數func進行更新
 var rdd = sc.makeRDD(1 to 5,2)
    rdd.foreach(println(_))
輸出結果 1 2 3 4 5

完整代碼託管於https://github.com/daizikaikou/learningSpark

參考:
https://spark.apache.org/docs/latest/rdd-programming-guide.html
https://www.jianshu.com/p/64aab52fbb21

發佈了118 篇原創文章 · 獲贊 114 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章