Spark的算子的分類

 

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算子

  Spark算子的作用,詳細見 http://www.cnblogs.com/zlslch/p/5723979.html

 

 

 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算子

  Spark算子的作用,詳細見http://www.cnblogs.com/zlslch/p/5723979.html

 

 

     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變量廣泛使用在應用中記錄當前的運行指標的情景。

 

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