常用Spark算子總結

    1. RDD的創建和保存
    • 1.1 textFile
      從HDFS中讀取一個文本文件
    • 1.2 makeRDD、parallelize
      都會創建一個新的ParallelCollectionRDD對象。如果makeRDD中的數據是Seq[T]結構,就會調用parallelize方法,不過makeRDD還可以將Seq[(T, Seq[String])]結構的數據轉變成RDD
      makeRDD源碼

      parallelize源碼
      makeRDD和parallelize可以自己設置分區數(numSlices),默認是defaultParallelism,即Task的默認并行度
      defaultParallelism源碼解釋
    • 1.3 saveAsTextFile
      將RDD保存為一個壓縮文本文件。
    1. 轉換算子
    • 2.1 不會shuffle的
      • 2.1.1 map、flatMap、mapPartitions
        new一個新的MapPartitionsRDD對象,并調用Scala相應集合類的map或flatMap方法。
        map和flatMap相應源碼
        MapPartitionsRDD類會將提供的方法作用于父RDD的每個分區。
        mapPartitions類源碼
        相比于map或flatMap,mapPartitions多一個參數preservesPartitioning(輸入true或false),判斷是否保留原分區。由于MapPartitionsRDD的preservesPartitioning參數默認是false,所以map和flatMap操作后分區肯定發生了變化,而mapPartitions則可以選擇保留原分區
      • 2.1.2 mapValues、flatMapValues
        mapValues和flatMapValues是在PairRDDFunctions類中定義的,而map和flatMap是在RDD類中定義的,說明mapValues和flatMapValues使用場景相對更小一點,其處理的RDD形式必須是鍵值對。從源碼來看,mapValues和flatMapValues只對鍵值對中的值進行處理,而且一定保證分區不變。
        mapValues和flatMapValues相應源碼
      • 2.1.3 coalesce
        對RDD進行合并分區。從源碼解釋上來看,coalesce是窄依賴關系,不需要shuffle(參數shuffle默認是false)。但源碼中也提到,如果合并后分區數很小,很可能導致計算在較少的節點上進行,此時可以將shuffle設置為true。


        coalesce源碼解釋

        coalesce源碼

        接著看源碼,可見coalesce具體進行過程受到shuffle參數的影響。當shuffle為false時,coalesce會創建新的CoalescedRDD類,CoalescedRDD類要求參數numPartitions大于0。coalesce主要是將父RDD合并成更少的分區,如果numPartitions大于原RDD分區數,那么新RDD分區數還是和原RDD分區數一樣;如果numPartitions小于原RDD分區數,就會對partitions進行分組(group),具體實現可見PartitionCoalescer類及其throwBalls方法。


        PartitionCoalescer類的throwBalls方法
    • 2.2 會shuffle的
      • 2.2.1 intersection
        intersection的實現主要是利用了cogroup方法。
        cogroup相應源碼
        cogroup會利用兩個PairRDD構建新的CoGroupRDD類,CoGroupRDD類是PairRDD,key是RDD_A和RDD_B的key總集,value是一個二元組(Interable(A_value), Interable(B_value)),是A和B同一key對應的value的集合。也就是說,cogroup最后得到的結果形式是(key, (iterable(v1), interable(v2)))。讓我們再回來看intersection的源碼實現
        intersection相應源碼
        intersection源碼首先將輸入的兩個RDD轉換成PairRDD形式(原RDD的值設置為key,value設置為null),然后基于這兩個PairRDD構造新的CogroupRDD(構造CogroupRDD時會有shuffle),最后進行進行判斷(只有interable(v1)和interable(v2)都不為空才會留下來),這樣就可以得到兩個RDD的交集。
        不過這里還有一點有些疑惑,那就是構建CogroupRDD時是如何確定結果RDD的分區數?源碼中與partition相關的代碼如下,但是我不太理解,\color{red}{待解決}
        CoGroupRDD分區邏輯源碼
      • 2.2.2 subtract
        subtract的實現是利用了SubtractedRDD類,調用subtractByKey方法,得到新的SubtractedRDD對象。subtract并沒有用cogroup算子,而是為之新創造了一個類SubtractedRDD。原因是cogroup會將兩個原RDD的key全部保留在內存中,但是SubtractedRDD中只會保留RDD_A的key,RDD_B的key會流式傳過來。當RDD_A小于RDD_B時該優化可以讓系統使用RDD_A的分區,而不用擔心RDD_B的大小導致內存不足。源碼中解釋如下:
        SubtractedRDD源碼解釋
        SubtractedRDD類的計算過程如下,沒太看懂,但是優化點代碼中解釋的比較清除——RDD_A全部存在內存中,RDD_B的key進行流式傳輸從而在A的key中排除B的key。這里主要是沒看懂\color{red}{oneToOneDependency}\color{red}{shuffleDependency}
        SubtractedRDD計算過程
      • 2.2.3 join、leftOuterJoin、fullOuterJoin
        join類型算子都是基于cogroup算子計算的。前面已經寫了,cogroup算子會將兩個RDD相同的key和相應value組合在一起,最后的結果形式是(key, (iterable(v1), interable(v2)))。最后用嵌套for循環組合(iterable(v1)和(iterable(v2)就可以得到join結果。不同的join實現的不同點在于兩個iterable組合時對空iterable的處理方式
      • 2.2.4 groupByKey
        將RDD中相同key的元素的value全部放到一個sequence中。這個操作是很耗費資源的,因為聚集過程中并沒有value的聚集,僅僅是收集起來放到一個sequence中。源碼中還提到groupByKey會將所有鍵值對都保存在內存中,所以可能會導致OOM。


        groupByKey源碼解釋
      • 2.2.5 reduceByKey、aggregateByKey
        如果是要對RDD中元素按key分組后對values進行聚合,那么就可以將groupByKey換成reduceByKey或aggregateByKey。
        reduceByKey算子和aggregateByKey算子都是基于combineByKeyWithClassTag實現的
        reduceByKey源碼

        aggregateByKey源碼
        可以看出相對于aggregateByKey算子,reduceByKey算子的限制更多,reduceByKey對combineByKeyWithClassTag的使用限制很多,一是輸入和輸出結果類型都限定為RDD元素類型二是map端和reduce端的聚集函數都是一樣的。
        補充:aggregateByKey還有個優勢在于combOp的輸入參數類型不需要一致,也就是說,使用reduceByKey的話,map聚合后的value得和沒聚合前的value類型一樣,但是aggregateByKey就沒這樣的要求。比如,如果要同時計算用戶的最大收入和最小收入,用aggregateByKey的話就可以直接對<用戶, 收入>進行操作,最后得到<用戶, <最大收入, 最小收入>>,但是用reduceByKey就得先將原數據形式改為<用戶, <收入, 收入>>,得保證和最終想得到的格式<用戶, <最大收入, 最小收入>>一樣。
        至于combineByKeyWithClassTag,我個人覺得就是對MapReduce中的combine函數的實現。
    1. 行動算子
    • 3.1 collect
      collect會調用sc.runJob將數據全部拉取到driver端存在一個Array中。


      collect源碼
    • 3.2 count、countByKey、countByValue
      count調用sc.runJob將數據拉回driver端并求和
      countByKey會將原PairRDD的值變成1L,然后利用reduceByKey和collect得到結果Map
      countByValue會將原RDD變成(value, null),然后基于countByKey得到結果Map
      count源碼

      countByKey源碼

      countByValue源碼
      后續再研究\color{red}{sc.runJob},這里挖個坑。
    • 3.3 foreach、foreachPartition
      先貼上源碼:


      foreach和foreachPartition源碼

      從源碼中可以看出,二者最大的區別在于對傳入方法f使用。前者是方法f作用于單個RDD元素,后者是方法f作用于整個RDD分區

    • 3.4 first、take、top
      take(num)是取回RDD的前num個元素,工作方式是先掃描一個工作分區,然后根據該分區返回結果評估是否還需要掃描其他分區以滿足num數量。注意,如果RDD是"Nothing"或"Null",則會返回異常。
      take源碼
      first就是take(1)
      first源碼
      top(num)是返回RDD中的最大的num個元素,基于takeOrdered實現。而takeOrdered是返回RDD中的最小的num個元素。
      takeOrdered源碼
      在takeOrdered實現中,RDD的每個分區排序存在一個iterator中,然后將所有分區的iterator拼接起來排序得到topN的數據(那如果RDD數量很大不是容易造成driver端的OOM么?看著這個算子要慎用!
    • 3.5 sortBy
      調用sortByKey方法,創建一個新的排好序的ShuffledRDD。這里根據名字猜測一下,既然新的RDD是ShuffledRDD,說明該RDD的創建應該是進行了shuffle,所以最后的結果應該是全局排序的。


      sortByKey源碼
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容