PageRank通俗說

PageRank是執行多次連接的一個迭代算法,因此它是RDD分區操作的很好demo,算法維護兩個數據集

  1. (pageID,listList) 包含每個頁面的相鄰頁面列表。

  2. (pageID,rank) 包含每個頁面的當前排序值, pageRank計算過程大致如下:

  3. 將每個頁面的排序值初始化爲1.0

  4. 在每次迭代中,對頁面p,向其每個相鄰頁面(有直接連接的頁面)發鬆一個值爲 rank(p)/numNeighbors(p)的貢獻值。

  5. 將每個頁面的排序值設定爲 0.15 + 0.85 *contributionsReceived 其中2 跟3 會重複循環幾次,在此過程中算法會逐漸收斂於每個頁面的實際PageRank值,實際操作中一般迭代10次。

package com.sowhat.spark

import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}

/**
 * links = (pageID,LinkList)
 * ranks = (pageID,rank)
 **/
object MyPageRank {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("pagerank")

    //創建SparkContext,該對象是提交spark App的入口
    val sc = new SparkContext(conf)

    val links: RDD[(String, Seq[String])] = sc.objectFile[(String, Seq[String])]("filepwd").partitionBy(new HashPartitioner(100)).persist()
    var ranks: RDD[(String, Double)] = links.mapValues(x => 1.0)

    for (i <- 0 until 10) {
      val totalRDD: RDD[(String, (Seq[String], Double))] = links.join(ranks)
      val contributions: RDD[(String, Double)] = totalRDD.flatMap(
        {
          case (pageID, (links, rank)) => links.map(dest => (dest, rank / links.size))
        }
      )
      ranks = contributions.reduceByKey(_ + _).mapValues(v => 0.15 + 0.85 * v)
    }
    ranks.saveAsTextFile("ranks")
  }

}

算法從ranksRDD的每個元素的值初始化爲1.0開始,然後每次迭代都都不斷的更新ranks值,其中主要優化部分如下。

  1. linksRDD每次迭代都會跟ranks發生連接操作,因此將大數據集links進行partitionBy 會節約相當多的網絡通信優化開銷。

  2. 跟上面的原因一樣,用persist 可以將數據保存早內存中,以供每次迭代使用。

  3. 我們在第一次創建ranks時, 我們用mapValues而不是map() 來保留父RDD links的分區方式,這樣對第一次連接操作開銷減少很多。

  4. 循環體中 reduceByKey後使用mapValues 因爲reduceByKey已經是哈希分區了,下一次迭代時候效率更快。

建議:爲最大化分區相關優化潛在作用,在無需更改元素鍵的時候儘量使用 mapValues 或 flatMapValues。

本文使用 mdnice 排版

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