reducByKey總結

reducByKey總結

在進行Spark開發算法時,最有用的一個函數就是reduceByKey。

reduceByKey的作用對像是(key, value)形式的rdd,而reduce有減少、壓縮之意,reduceByKey的作用就是對相同key的數據進行處理,最終每個key只保留一條記錄。

保留一條記錄通常有兩種結果。一種是隻保留我們希望的信息,比如每個key出現的次數。第二種是把value聚合在一起形成列表,這樣後續可以對value做進一步的操作,比如排序。

常用方式舉例

比如現在我們有數據goodsSale:RDD[(String, String)],兩個字段分別是goodsid、單個訂單中的銷售額,現在我們需要統計每個goodsid的銷售額。

我們只需要保留每個goodsid的累記銷售額,可以使用如下語句來實現:

val goodsSaleSum = goodsSale.reduceByKey((x,y) => x+y)

熟悉之後你可以使用更簡潔的方式:

val goodsSaleSum = goodsSale.reduceByKey(_+_)

reduceByKey會尋找相同key的數據,當找到這樣的兩條記錄時會對其value(分別記爲x,y)做(x,y) => x+y的處理,即只保留求和之後的數據作爲value。反覆執行這個操作直至每個key只留下一條記錄。

現在假設goodsSaleSum還有一個字段類目id,即 RDD[(String, String, String)] 形式,三個字段分別是類目id、goodsid、總銷量,現在我們要獲得第個類目id下銷量最高的一個商品。

上一步聚是保留value求和之後的數據,而這裏其實我們只需要保留銷量更高的那條記錄。不過我們不能直接對RDD[(String, String, String)]類型的數據使用reduceByKey方法,因爲這並不是一個(key, value)形式的數據,所以需要使用map方法轉化一下類型。

val catGmvTopGoods = goodsSaleSum.map(x => (x._1, (x._2, x._3)))

    .reduceByKey((x, y) => if (x._2.toDouble > y._2.toDouble) x else y)

    .map(x => (x._1, x._2._1, x._2._2)

再進一步,假設現在我們有一個任務:推薦5個銷售額最高的類目,併爲每個類目推薦一個銷售額最高的商品,而我們的數據就是上述RDD[(String, String, String)類型的goodsSaleSum。

這需要兩步,一是計算每個類目的銷售額,這和舉的第一個例子一樣。二是找出每個類目下銷量最高的商品,這和第二個例子一樣。實際上,我們可以只實用一個reduceByKey就達到上面的目的。

val catIdGmvTopGoods = goodsSaleSum.map(x => (x._1, (x._2, x._3, x._3)))

    .reduceByKey((x, y) => if (x._2 > y._2) (x._1, x._2, x._3+y._3) else (y._1, y._2, x._3+y._3))

    .map( x => (x._1, x._2._1, x._2._2, x._2._3)

    .sortBy(_._3, false)

    .take(5)

由於我們需要計算每個類目的總銷售額,同時需要保留商品的銷售額,所以先使用map增加一個字段用來記錄類目的總銷售額。這樣一來,我們就可以使用reduceByKey同時完成前兩個例子的操作。

剩下的就是進行排序並獲取前5條記錄。

聚合方式舉例

上述的三個例子都是隻保留需要的信息,但有時我們需要將value聚合在一起進行排序操作,比如對每個類目下的商品按銷售額進行排序。

假設我們的數據是 RDD[(String, String, String)],三個字段分別是類目id、goodsid、銷售額。

若是使用sql,那我們直接用row_number函數就可以很簡單的使用分類目排序這個任務。

但由於spark-sql佔用的資源會比RDD多不少,在開發任務時並不建議使用spark-sql。

我們的方法是通過reduceByKey把商品聚合成一個List,然後對這個List進行排序,再使用flatMapValues攤平數據。

我們在使用reduceyByKey時會注意到,兩個value聚合後的數據類型必須和之前一致。

所以在聚合商品時我們也需要保證這點,通常有兩種方法,一是使用ListBuffer,即可變長度的List。二是使用String,以分隔符來區分商品和銷售額。下面我們使用第一種方式完成這個任務。

val catIdGoodsIdSorted = goodsGmvSum.map(x => (x._1, ListBuffer(x._2, x._3.toDouble)))

    .reduceByKey((x, y) => x++y)

    .flatMapValues( x => x.toList.sortBy(_._2).reverse.zipWithIndex)

上述zipWithIndex給列表增加一個字段,用來記錄元素的位置信息。而flatMapValues可以把List的每個元素單獨拆成一條記錄,詳細的說明可以參考我寫的另一篇文章Spark入門-常用函數彙總

小結

我在本文中介紹了reduceByKey的三種作用:

  1. 求和彙總
  2. 獲得每個key下value最大的記錄
  3. 聚合value形成一個List之後進行排序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章