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 排版

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