PageRank是一种从RDD分区中获益的算法,刚学完RDD分区,便以它为例吧。
PageRank用来根据外部文档指向一个文档的链接,对集合中每个文档的重要程度赋一个度量值。该算法可以用于对网页进行排序,也可以用于排序科技文章或社交网站中有影响的用户。
PageRank是执行多次连接的一个迭代算法,算法维护两个数据集:一个由(pageID,linkList)的元素组成,包含每个页面的相邻页面的列表;另一个由(pageID,rank)元素组成,包含每个页面的当前排序值。
计算步骤:
(1)将每个页面的排序值初始化为1.0
(2)在每次迭代中,对页面p,向其每个相邻页面(有直接链接的页面)发送一个值为rank(p)/numNeighbors(p)的贡献值
(3)将每个页面的排序值设置为0.15+0.85*contributionsReceived
最后两步会重复几个循环,在此过程中,算法会逐渐收敛于每个页面的实际PageRank值,在实际操作中,收敛通常需要大约10轮迭代。
scala版代码:
初始数据:假设原始数据为:100页面中有两个直接链接指向页面101,102;102页面中两个直接链接指向页面100,105。。。。。
scala> val data=Seq(("100",Seq("101","102")),("102",Seq("100","105")),("103",Seq("104","101")),("105",Seq("102","107"))) data: Seq[(String, Seq[String])] = List((100,List(101, 102)), (102,List(100, 105)), (103,List(104, 101)), (105,List(102, 107)))创建rdd并使用哈希分区,分区后缓存在内存中:
scala> import org.apache.spark._//导包 import org.apache.spark._
scala> val links=sc.parallelize(data).partitionBy(new HashPartitioner(100)).persist() links: org.apache.spark.rdd.RDD[(String, Seq[String])] = ShuffledRDD[29] at partitionBy at <console>:41
这里有一个坑,如果用makeRDD去创建,RDD不是键值对RDD,以后还是使用parallelize方法吧。。。 makeRDD和parallelize的区别 makeRDD和parallelize的区别
scala> val links=sc.makeRDD(data) links: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[26] at makeRDD at <console>:32将每个页面的排序值初始化为1.0,;由于使用mapValues(没有改变键)生成的RDD,所以和links的分区方式一样
scala> var ranks=links.mapValues(v=>1.0) ranks: org.apache.spark.rdd.RDD[(String, Double)] = MapPartitionsRDD[32] at mapValues at <console>:43运行10轮PageRank迭代
scala> for(i<-0 until 10){ | val contributions=links.join(rank).flatMap{//注意map和flatMap的区别 只要是元素中是集合,要变成单个元素就用flatMap() | case(pageId,(links,rank))=>links.map(dest=>(dest,rank/links.size))//每个页面收到的贡献值 | } | ranks =contributions.reduceByKey((x,y)=>x+y).mapValues(v=>0.15+0.85*v)//reduceByKey()结果已经是哈希分区,这样下次循环中映射操作的结果再次与links进行连接操作就会更高效 | }遍历ranks得到最终排名
scala> ranks foreach println (100,0.575) (101,1.0) (102,1.0) (104,0.575) (105,0.575) (107,0.575)
map和flatMap的区别:
flatMap:
scala> val contributions=links.join(rank).flatMap{ case(pageId,(links,rank))=>links } contributions: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[173] at flatMap at <console>:45
scala> contributions foreach println 101 102 100 105 104 101 102 107map:
scala> val contributions=links.join(rank).map{ case(pageId,(links,rank))=>links } contributions: org.apache.spark.rdd.RDD[Seq[String]] = MapPartitionsRDD[181] at map at <console>:45 scala> contributions foreach println List(101, 102) List(100, 105) List(104, 101) List(102, 107)
map返回一个大集合,大集合中每个元素是一个小集合,而flatMap返回一个大集合,大集合中每个元素是String元素,因为 flatMap会将集合拍扁成单个元素,所以要想将集合变成单个元素就用flatMap
这是关于rdd分区的一个例子,为了最大化分区相关优化的潜在作用,应该在无需改变元素的键时尽量使用mapValues()或flatMapValues() 而不是map()、flatMap()