Spark常用算子概述

轉載自 https://blog.csdn.net/u010199356



Spark的算子的分類

   從大方向來說,Spark 算子大致可以分爲以下兩類:

     1)Transformation 變換/轉換算子:這種變換並不觸發提交作業,完成作業中間過程處理

     Transformation 操作是延遲計算的,也就是說從一個RDD 轉換生成另一個 RDD 的轉換操作不是馬上執行,需要等到有 Action 操作的時候纔會真正觸發運算。

     2)Action 行動算子:這類算子會觸發 SparkContext 提交 Job 作業

      Action 算子會觸發 Spark 提交作業(Job),並將數據輸出 Spark系統。

 

  從小方向來說,Spark 算子大致可以分爲以下三類:

  1)Value數據類型的Transformation算子,這種變換並不觸發提交作業,針對處理的數據項是Value型的數據。
  2)Key-Value數據類型的Transfromation算子,這種變換並不觸發提交作業,針對處理的數據項是Key-Value型的數據對。

  3)Action算子,這類算子會觸發SparkContext提交Job作業。

 

 

1)Value數據類型的Transformation算子  

  一、輸入分區與輸出分區一對一型

    1、map算子

    2、flatMap算子

    3、mapPartitions算子

    4、glom算子

  二、輸入分區與輸出分區多對一型 

    5、union算子

    6、cartesian算子

  三、輸入分區與輸出分區多對多型

    7、grouBy算子

  四、輸出分區爲輸入分區子集型

    8、filter算子

    9、distinct算子

    10、subtract算子

    11、sample算子

        12、takeSample算子

   五、Cache型

    13、cache算子  

    14、persist算子

 

2)Key-Value數據類型的Transfromation算子

  一、輸入分區與輸出分區一對一

    15、mapValues算子

  二、對單個RDD或兩個RDD聚集

   單個RDD聚集

    16、combineByKey算子

    17、reduceByKey算子

    18、partitionBy算子

   兩個RDD聚集

    19、Cogroup算子

  三、連接

    20、join算子

    21、leftOutJoin和 rightOutJoin算子

 

 

 3)Action算子

  一、無輸出

    22、foreach算子

  二、HDFS

    23、saveAsTextFile算子

    24、saveAsObjectFile算子

  三、Scala集合和數據類型

    25、collect算子

    26、collectAsMap算子

      27、reduceByKeyLocally算子

      28、lookup算子

    29、count算子

    30、top算子

    31、reduce算子

    32、fold算子

    33、aggregate算子

 

 

     1. Transformations 算子
 (1) map

  將原來 RDD 的每個數據項通過 map 中的用戶自定義函數 f 映射轉變爲一個新的元素。源碼中 map 算子相當於初始化一個 RDD, 新 RDD 叫做 MappedRDD(this, sc.clean(f))。

     圖 1中每個方框表示一個 RDD 分區,左側的分區經過用戶自定義函數 f:T->U 映射爲右側的新 RDD 分區。但是,實際只有等到 Action算子觸發後,這個 f 函數纔會和其他函數在一個stage 中對數據進行運算。在圖 1 中的第一個分區,數據記錄 V1 輸入 f,通過 f 轉換輸出爲轉換後的分區中的數據記錄 V’1。
                            

      圖1    map 算子對 RDD 轉換                   

 

    (2) flatMap
     將原來 RDD 中的每個元素通過函數 f 轉換爲新的元素,並將生成的 RDD 的每個集合中的元素合併爲一個集合,內部創建 FlatMappedRDD(this,sc.clean(f))。
  圖 2 表 示 RDD 的 一 個 分 區 ,進 行 flatMap函 數 操 作, flatMap 中 傳 入 的 函 數 爲 f:T->U, T和 U 可以是任意的數據類型。將分區中的數據通過用戶自定義函數 f 轉換爲新的數據。外部大方框可以認爲是一個 RDD 分區,小方框代表一個集合。 V1、 V2、 V3 在一個集合作爲 RDD 的一個數據項,可能存儲爲數組或其他容器,轉換爲V’1、 V’2、 V’3 後,將原來的數組或容器結合拆散,拆散的數據形成爲 RDD 中的數據項。

        圖2     flapMap 算子對 RDD 轉換


    (3) mapPartitions
      mapPartitions 函 數 獲 取 到 每 個 分 區 的 迭 代器,在 函 數 中 通 過 這 個 分 區 整 體 的 迭 代 器 對整 個 分 區 的 元 素 進 行 操 作。 內 部 實 現 是 生 成
MapPartitionsRDD。圖 3 中的方框代表一個 RDD 分區。圖 3 中,用戶通過函數 f (iter)=>iter.f ilter(_>=3) 對分區中所有數據進行過濾,大於和等於 3 的數據保留。一個方塊代表一個 RDD 分區,含有 1、 2、 3 的分區過濾只剩下元素 3。

    圖3  mapPartitions 算子對 RDD 轉換

 

  (4)glom

  glom函數將每個分區形成一個數組,內部實現是返回的GlommedRDD。 圖4中的每個方框代表一個RDD分區。圖4中的方框代表一個分區。 該圖表示含有V1、 V2、 V3的分區通過函數glom形成一數組Array[(V1),(V2),(V3)]。

      圖 4   glom算子對RDD轉換

 

     (5) union
      使用 union 函數時需要保證兩個 RDD 元素的數據類型相同,返回的 RDD 數據類型和被合併的 RDD 元素數據類型相同,並不進行去重操作,保存所有元素。如果想去重
可以使用 distinct()。同時 Spark 還提供更爲簡潔的使用 union 的 API,通過 ++ 符號相當於 union 函數操作
     圖 5 中左側大方框代表兩個 RDD,大方框內的小方框代表 RDD 的分區。右側大方框代表合併後的 RDD,大方框內的小方框代表分區。

  含有V1、V2、U1、U2、U3、U4的RDD和含有V1、V8、U5、U6、U7、U8的RDD合併所有元素形成一個RDD。V1、V1、V2、V8形成一個分區,U1、U2、U3、U4、U5、U6、U7、U8形成一個分區。

  圖 5  union 算子對 RDD 轉換 



  (6) cartesian
       對 兩 個 RDD 內 的 所 有 元 素 進 行 笛 卡 爾 積 操 作。 操 作 後, 內 部 實 現 返 回CartesianRDD。圖6中左側大方框代表兩個 RDD,大方框內的小方框代表 RDD 的分區。右側大方框代表合併後的 RDD,大方框內的小方框代表分區。圖6中的大方框代表RDD,大方框中的小方框代表RDD分區。
      例 如: V1 和 另 一 個 RDD 中 的 W1、 W2、 Q5 進 行 笛 卡 爾 積 運 算 形 成 (V1,W1)、(V1,W2)、 (V1,Q5)。
     

       圖 6  cartesian 算子對 RDD 轉換

 

  (7) groupBy
  groupBy :將元素通過函數生成相應的 Key,數據就轉化爲 Key-Value 格式,之後將 Key 相同的元素分爲一組。
  函數實現如下:
  1)將用戶函數預處理:
  val cleanF = sc.clean(f)
  2)對數據 map 進行函數操作,最後再進行 groupByKey 分組操作。

     this.map(t => (cleanF(t), t)).groupByKey(p)
  其中, p 確定了分區個數和分區函數,也就決定了並行化的程度。

  圖7 中方框代表一個 RDD 分區,相同key 的元素合併到一個組。例如 V1 和 V2 合併爲 V, Value 爲 V1,V2。形成 V,Seq(V1,V2)。

  圖 7 groupBy 算子對 RDD 轉換

 

  (8) filter
    filter 函數功能是對元素進行過濾,對每個 元 素 應 用 f 函 數, 返 回 值 爲 true 的 元 素 在RDD 中保留,返回值爲 false 的元素將被過濾掉。 內 部 實 現 相 當 於 生 成 FilteredRDD(this,sc.clean(f))。
    下面代碼爲函數的本質實現:
    deffilter(f:T=>Boolean):RDD[T]=newFilteredRDD(this,sc.clean(f))
  圖 8 中每個方框代表一個 RDD 分區, T 可以是任意的類型。通過用戶自定義的過濾函數 f,對每個數據項操作,將滿足條件、返回結果爲 true 的數據項保留。例如,過濾掉 V2 和 V3 保留了 V1,爲區分命名爲 V’1。

  圖 8  filter 算子對 RDD 轉換
     

  (9)distinct

  distinct將RDD中的元素進行去重操作。圖9中的每個方框代表一個RDD分區,通過distinct函數,將數據去重。 例如,重複數據V1、 V1去重後只保留一份V1。

    圖9  distinct算子對RDD轉換

 

  (10)subtract

  subtract相當於進行集合的差操作,RDD 1去除RDD 1和RDD 2交集中的所有元素。圖10中左側的大方框代表兩個RDD,大方框內的小方框代表RDD的分區。 右側大方框
代表合併後的RDD,大方框內的小方框代表分區。 V1在兩個RDD中均有,根據差集運算規則,新RDD不保留,V2在第一個RDD有,第二個RDD沒有,則在新RDD元素中包含V2。
  

          圖10   subtract算子對RDD轉換

 

  (11) sample
       sample 將 RDD 這個集合內的元素進行採樣,獲取所有元素的子集。用戶可以設定是否有放回的抽樣、百分比、隨機種子,進而決定採樣方式。內部實現是生成 SampledRDD(withReplacement, fraction, seed)。
  函數參數設置:
‰   withReplacement=true,表示有放回的抽樣。
‰   withReplacement=false,表示無放回的抽樣。
  圖 11中 的 每 個 方 框 是 一 個 RDD 分 區。 通 過 sample 函 數, 採 樣 50% 的 數 據。V1、 V2、 U1、 U2、U3、U4 採樣出數據 V1 和 U1、 U2 形成新的 RDD。

     

       圖11  sample 算子對 RDD 轉換

 

  (12)takeSample

  takeSample()函數和上面的sample函數是一個原理,但是不使用相對比例採樣而是按設定的採樣個數進行採樣,同時返回結果不再是RDD,而是相當於對採樣後的數據進行
Collect(),返回結果的集合爲單機的數組。
  圖12中左側的方框代表分佈式的各個節點上的分區,右側方框代表單機上返回的結果數組。 通過takeSample對數據採樣,設置爲採樣一份數據,返回結果爲V1。

    圖12    takeSample算子對RDD轉換

 

  (13) cache
     cache 將 RDD 元素從磁盤緩存到內存。 相當於 persist(MEMORY_ONLY) 函數的功能。
     圖13 中每個方框代表一個 RDD 分區,左側相當於數據分區都存儲在磁盤,通過 cache 算子將數據緩存在內存。
      

      圖 13 Cache 算子對 RDD 轉換

 

  (14) persist
      persist 函數對 RDD 進行緩存操作。數據緩存在哪裏依據 StorageLevel 這個枚舉類型進行確定。 有以下幾種類型的組合(見10), DISK 代表磁盤,MEMORY 代表內存, SER 代表數據是否進行序列化存儲。

  下面爲函數定義, StorageLevel 是枚舉類型,代表存儲模式,用戶可以通過圖 14-1 按需進行選擇。
  persist(newLevel:StorageLevel)
  圖 14-1 中列出persist 函數可以進行緩存的模式。例如,MEMORY_AND_DISK_SER 代表數據可以存儲在內存和磁盤,並且以序列化的方式存儲,其他同理。

            圖 14-1  persist 算子對 RDD 轉換

  圖 14-2 中方框代表 RDD 分區。 disk 代表存儲在磁盤, mem 代表存儲在內存。數據最初全部存儲在磁盤,通過 persist(MEMORY_AND_DISK) 將數據緩存到內存,但是有的分區無法容納在內存,將含有 V1、 V2、 V3 的RDD存儲到磁盤,將含有U1,U2的RDD仍舊存儲在內存。

      圖 14-2   Persist 算子對 RDD 轉換

 

  (15) mapValues
      mapValues :針對(Key, Value)型數據中的 Value 進行 Map 操作,而不對 Key 進行處理。

    圖 15 中的方框代表 RDD 分區。 a=>a+2 代表對 (V1,1) 這樣的 Key Value 數據對,數據只對 Value 中的 1 進行加 2 操作,返回結果爲 3。

     

      圖 15   mapValues 算子 RDD 對轉換

 

  (16) combineByKey
  下面代碼爲 combineByKey 函數的定義:
  combineByKey[C](createCombiner:(V) C,
  mergeValue:(C, V) C,
  mergeCombiners:(C, C) C,
  partitioner:Partitioner,
  mapSideCombine:Boolean=true,
  serializer:Serializer=null):RDD[(K,C)]

說明:
‰   createCombiner: V => C, C 不存在的情況下,比如通過 V 創建 seq C。
‰   mergeValue: (C, V) => C,當 C 已經存在的情況下,需要 merge,比如把 item V
加到 seq C 中,或者疊加。
   mergeCombiners: (C, C) => C,合併兩個 C。
‰   partitioner: Partitioner, Shuff le 時需要的 Partitioner。
‰   mapSideCombine : Boolean = true,爲了減小傳輸量,很多 combine 可以在 map
端先做,比如疊加,可以先在一個 partition 中把所有相同的 key 的 value 疊加,
再 shuff le。
‰   serializerClass: String = null,傳輸需要序列化,用戶可以自定義序列化類:

  例如,相當於將元素爲 (Int, Int) 的 RDD 轉變爲了 (Int, Seq[Int]) 類型元素的 RDD。圖 16中的方框代表 RDD 分區。如圖,通過 combineByKey, 將 (V1,2), (V1,1)數據合併爲( V1,Seq(2,1))。
  

      圖 16  comBineByKey 算子對 RDD 轉換

 

  (17) reduceByKey
     reduceByKey 是比 combineByKey 更簡單的一種情況,只是兩個值合併成一個值,( Int, Int V)to (Int, Int C),比如疊加。所以 createCombiner reduceBykey 很簡單,就是直接返回 v,而 mergeValue和 mergeCombiners 邏輯是相同的,沒有區別。
    函數實現:
    def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)]
= {
combineByKey[V]((v: V) => v, func, func, partitioner)
}
  圖17中的方框代表 RDD 分區。通過用戶自定義函數 (A,B) => (A + B) 函數,將相同 key 的數據 (V1,2) 和 (V1,1) 的 value 相加運算,結果爲( V1,3)。
     

        圖 17 reduceByKey 算子對 RDD 轉換

 

  (18)partitionBy

  partitionBy函數對RDD進行分區操作。
  函數定義如下。
  partitionBy(partitioner:Partitioner)
  如果原有RDD的分區器和現有分區器(partitioner)一致,則不重分區,如果不一致,則相當於根據分區器生成一個新的ShuffledRDD。
  圖18中的方框代表RDD分區。 通過新的分區策略將原來在不同分區的V1、 V2數據都合併到了一個分區。

 

    圖18  partitionBy算子對RDD轉換

 

 (19)Cogroup

   cogroup函數將兩個RDD進行協同劃分,cogroup函數的定義如下。
  cogroup[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))]
  對在兩個RDD中的Key-Value類型的元素,每個RDD相同Key的元素分別聚合爲一個集合,並且返回兩個RDD中對應Key的元素集合的迭代器。
  (K, (Iterable[V], Iterable[W]))
  其中,Key和Value,Value是兩個RDD下相同Key的兩個數據集合的迭代器所構成的元組。
  圖19中的大方框代表RDD,大方框內的小方框代表RDD中的分區。 將RDD1中的數據(U1,1)、 (U1,2)和RDD2中的數據(U1,2)合併爲(U1,((1,2),(2)))。

        圖19  Cogroup算子對RDD轉換

 

   (20) join
       join 對兩個需要連接的 RDD 進行 cogroup函數操作,將相同 key 的數據能夠放到一個分區,在 cogroup 操作之後形成的新 RDD 對每個key 下的元素進行笛卡爾積的操作,返回的結果再展平,對應 key 下的所有元組形成一個集合。最後返回 RDD[(K, (V, W))]。
  下 面 代 碼 爲 join 的 函 數 實 現, 本 質 是通 過 cogroup 算 子 先 進 行 協 同 劃 分, 再 通 過flatMapValues 將合併的數據打散。
       this.cogroup(other,partitioner).f latMapValues{case(vs,ws) => for(v<-vs;w<-ws)yield(v,w) }
圖 20是對兩個 RDD 的 join 操作示意圖。大方框代表 RDD,小方框代表 RDD 中的分區。函數對相同 key 的元素,如 V1 爲 key 做連接後結果爲 (V1,(1,1)) 和 (V1,(1,2))。

                    圖 20   join 算子對 RDD 轉換

 

  (21)eftOutJoinrightOutJoin

  LeftOutJoin(左外連接)和RightOutJoin(右外連接)相當於在join的基礎上先判斷一側的RDD元素是否爲空,如果爲空,則填充爲空。 如果不爲空,則將數據進行連接運算,並
返回結果。
下面代碼是leftOutJoin的實現。
if (ws.isEmpty) {
vs.map(v => (v, None))
} else {
for (v <- vs; w <- ws) yield (v, Some(w))
}

 

2. Actions 算子
  本質上在 Action 算子中通過 SparkContext 進行了提交作業的 runJob 操作,觸發了RDD DAG 的執行。
例如, Action 算子 collect 函數的代碼如下,感興趣的讀者可以順着這個入口進行源碼剖析:

/**
* Return an array that contains all of the elements in this RDD.
*/
def collect(): Array[T] = {
/* 提交 Job*/
val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)
Array.concat(results: _*)
}


  (22) foreach
  foreach 對 RDD 中的每個元素都應用 f 函數操作,不返回 RDD 和 Array, 而是返回Uint。圖22表示 foreach 算子通過用戶自定義函數對每個數據項進行操作。本例中自定義函數爲 println(),控制檯打印所有數據項。
  

      圖 22 foreach 算子對 RDD 轉換

 

  (23) saveAsTextFile
  函數將數據輸出,存儲到 HDFS 的指定目錄。

下面爲 saveAsTextFile 函數的內部實現,其內部
  通過調用 saveAsHadoopFile 進行實現:
this.map(x => (NullWritable.get(), new Text(x.toString))).saveAsHadoopFile[TextOutputFormat[NullWritable, Text]](path)
將 RDD 中的每個元素映射轉變爲 (null, x.toString),然後再將其寫入 HDFS。
  圖 23中左側方框代表 RDD 分區,右側方框代表 HDFS 的 Block。通過函數將RDD 的每個分區存儲爲 HDFS 中的一個 Block。

  

            圖 23   saveAsHadoopFile 算子對 RDD 轉換

 

  (24)saveAsObjectFile

  saveAsObjectFile將分區中的每10個元素組成一個Array,然後將這個Array序列化,映射爲(Null,BytesWritable(Y))的元素,寫入HDFS爲SequenceFile的格式。
  下面代碼爲函數內部實現。
  map(x=>(NullWritable.get(),new BytesWritable(Utils.serialize(x))))
  圖24中的左側方框代表RDD分區,右側方框代表HDFS的Block。 通過函數將RDD的每個分區存儲爲HDFS上的一個Block。

            圖24 saveAsObjectFile算子對RDD轉換

 

 (25) collect
  collect 相當於 toArray, toArray 已經過時不推薦使用, collect 將分佈式的 RDD 返回爲一個單機的 scala Array 數組。在這個數組上運用 scala 的函數式操作。
  圖 25中左側方框代表 RDD 分區,右側方框代表單機內存中的數組。通過函數操作,將結果返回到 Driver 程序所在的節點,以數組形式存儲。

  圖 25   Collect 算子對 RDD 轉換 

 

  (26)collectAsMap

  collectAsMap對(K,V)型的RDD數據返回一個單機HashMap。 對於重複K的RDD元素,後面的元素覆蓋前面的元素。
  圖26中的左側方框代表RDD分區,右側方框代表單機數組。 數據通過collectAsMap函數返回給Driver程序計算結果,結果以HashMap形式存儲。

 

          圖26 CollectAsMap算子對RDD轉換

 

   (27)reduceByKeyLocally

  實現的是先reduce再collectAsMap的功能,先對RDD的整體進行reduce操作,然後再收集所有結果返回爲一個HashMap。

 

   (28)lookup

下面代碼爲lookup的聲明。
lookup(key:K):Seq[V]
Lookup函數對(Key,Value)型的RDD操作,返回指定Key對應的元素形成的Seq。 這個函數處理優化的部分在於,如果這個RDD包含分區器,則只會對應處理K所在的分區,然後返回由(K,V)形成的Seq。 如果RDD不包含分區器,則需要對全RDD元素進行暴力掃描處理,搜索指定K對應的元素。
  圖28中的左側方框代表RDD分區,右側方框代表Seq,最後結果返回到Driver所在節點的應用中。

      圖28  lookup對RDD轉換

 

  (29) count
  count 返回整個 RDD 的元素個數。
  內部函數實現爲:
  defcount():Long=sc.runJob(this,Utils.getIteratorSize_).sum
  圖 29中,返回數據的個數爲 5。一個方塊代表一個 RDD 分區。

     圖29 count 對 RDD 算子轉換

 

  (30)top

top可返回最大的k個元素。 函數定義如下。
top(num:Int)(implicit ord:Ordering[T]):Array[T]

相近函數說明如下。
·top返回最大的k個元素。
·take返回最小的k個元素。
·takeOrdered返回最小的k個元素,並且在返回的數組中保持元素的順序。
·first相當於top(1)返回整個RDD中的前k個元素,可以定義排序的方式Ordering[T]。
返回的是一個含前k個元素的數組。

 

  (31)reduce

  reduce函數相當於對RDD中的元素進行reduceLeft函數的操作。 函數實現如下。
  Some(iter.reduceLeft(cleanF))
  reduceLeft先對兩個元素<K,V>進行reduce函數操作,然後將結果和迭代器取出的下一個元素<k,V>進行reduce函數操作,直到迭代器遍歷完所有元素,得到最後結果。在RDD中,先對每個分區中的所有元素<K,V>的集合分別進行reduceLeft。 每個分區形成的結果相當於一個元素<K,V>,再對這個結果集合進行reduceleft操作。
  例如:用戶自定義函數如下。
  f:(A,B)=>(A._1+”@”+B._1,A._2+B._2)
  圖31中的方框代表一個RDD分區,通過用戶自定函數f將數據進行reduce運算。 示例
最後的返回結果爲V1@[1]V2U!@U2@U3@U4,12。

 

圖31 reduce算子對RDD轉換

 

  (32)fold

  fold和reduce的原理相同,但是與reduce不同,相當於每個reduce時,迭代器取的第一個元素是zeroValue。
  圖32中通過下面的用戶自定義函數進行fold運算,圖中的一個方框代表一個RDD分區。 讀者可以參照reduce函數理解。
  fold((”V0@”,2))( (A,B)=>(A._1+”@”+B._1,A._2+B._2))

          圖32  fold算子對RDD轉換

 

   (33)aggregate

   aggregate先對每個分區的所有元素進行aggregate操作,再對分區的結果進行fold操作。
  aggreagate與fold和reduce的不同之處在於,aggregate相當於採用歸併的方式進行數據聚集,這種聚集是並行化的。 而在fold和reduce函數的運算過程中,每個分區中需要進行串行處理,每個分區串行計算完結果,結果再按之前的方式進行聚集,並返回最終聚集結果。
  函數的定義如下。
aggregate[B](z: B)(seqop: (B,A) => B,combop: (B,B) => B): B
  圖33通過用戶自定義函數對RDD 進行aggregate的聚集操作,圖中的每個方框代表一個RDD分區。
  rdd.aggregate(”V0@”,2)((A,B)=>(A._1+”@”+B._1,A._2+B._2)),(A,B)=>(A._1+”@”+B_1,A._@+B_.2))
  最後,介紹兩個計算模型中的兩個特殊變量。
  廣播(broadcast)變量:其廣泛用於廣播Map Side Join中的小表,以及廣播大變量等場景。 這些數據集合在單節點內存能夠容納,不需要像RDD那樣在節點之間打散存儲。
Spark運行時把廣播變量數據發到各個節點,並保存下來,後續計算可以複用。 相比Hadoo的distributed cache,廣播的內容可以跨作業共享。 Broadcast的底層實現採用了BT機制。

        圖33  aggregate算子對RDD轉換

  ②代表V。
  ③代表U。
  accumulator變量:允許做全局累加操作,如accumulator變量廣泛使用在應用中記錄當前的運行指標的情景。

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