前言
控制数据分布以获得最少的网络传输可以极大地提升整体性能。
如果给定RDD只需要被扫描一次(例如大小表join中的小表),我们完全没有必要对其预先进行分区处理,只有当数据集多次在诸如连接这种基于键的操作中使用时(大表),分区才有帮助。
尽管Spark没有给出显示控制每个键具体落在哪一个工作节点上的方法,但是Spark可以确保同一组的键出现在同一个节点上
(例1:使用hash分区将一个RDD分成了100个分区,此时键的哈希值对100取模的结果相同的记录会被放在一个节点上。)
(例2:使用范围分区,将键在一定范围区间内的记录都放在一个节点上。)
1.获取RDD的分区方式
注意:如果要在后续操作中使用partitioned,那就应该在定义partitioned时,在第三行输入的最后加上persist()。
原因:如果不调用persist(),后续的RDD操作会对partitioned整个谱系重新求值,这会导致pairs一遍又一遍地进行哈希分区操作。
import org.apache.spark
import org.apache.spark.{SparkConf, SparkContext}
object rdd_fqfs {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setMaster("local[*]")
.setAppName("RDD-Join")
val sc = SparkContext.getOrCreate(conf)
val pairs =sc.parallelize(List((1,1),(2,2),(3,3)))
val partitioned = pairs.partitionBy(new spark.HashPartitioner(8)).persist()
println(partitioned.partitioner)
}
}
2.从分区中获益的操作
就Spark1.0而言,能从分区中获益的操作有:cogroup()、groupwith()、join()、leftOuterJoin()、rightOuterJoin()、groupByKey()、reduceByKey()、combinerByKey()、lookup()
如果两个RDD使用同样的分区方式。并且他们还缓存在同样的机器上(比如一个RDD是通过mapValues()从另一个RDD中创建出来的,这两个RDD就会拥有相同的键和分区方式),或者其中一个RDD还没有倍计算出来,那么跨节点的数据混洗就不会发生了。
3.影响分区方式的操作
Spark内部知道各操作会如何影响分区方式,并将会对数据进行分区的操作的结果RDD自动设置为对应的分区器。
列出所有会为生成的结果RDD设好分区方式的操作:cogroup()、groupwith()、join()、leftOuterJoin()、rightOuterJoin()、groupByKey()、reduceByKey()、combinerByKey()、partitionByKey()、sort()、
[mapValues()、flatMapValues()、filter()][这三个-如果父RDD有分区方式的话]
对于二元操作,如果其中的一个父RDD已经设置过分区方式,那么结果就会采用那种分区方式;如果两个父RDD都设置过分区方式,结果RDD采用第一个父RDD的分区方式。(简单说:一个随父,两个随前父)
4.PageRank
PageRank是一种从RDD分区中获益的执行多次连接的迭代算法。
可用于对网页排序,也可以用于排序科技文章或社交网络中有影响的用户。
计算步骤:
(1)将每个页面的排序值初始化为1.0
(2)在每次迭代中,对页面p,向其每个相邻页面(有直接链接的页面)发送一个值为rank(p)/numNeighbors(p)的贡献值
(3)将每个页面的排序值设为0.15+0.85*contributionsReceived
最后两步会重复几个循环,在此过程中,算法会逐渐收敛于每个页面的实际PageRank值,在实际操作中,收敛需要10个迭代
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
object pagerank_test2 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setMaster("local[*]")
.setAppName("RDD-Join")
val sc = SparkContext.getOrCreate(conf)
//假设相邻页面列表以Spark objectFile的形式存储
val links = sc.objectFile[(String,Seq[String])]("links")
.partitionBy(new HashPartitioner(100))
.persist()
//将每个页面的排序值初始化为1.0;由于使用mapValues,生成的RDD的分区方式会和“links”的一样
var ranks = links.mapValues(v=>1.0)
//运行10轮迭代
for(i <-0 until 10){
val contributions = links.join(ranks).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)
}
//写出最终排名
ranks.saveAsTextFile("ranks")
}
}
import org.apache.log4j.{Level,Logger}
import org.apache.spark.graphx.{Graph, GraphLoader}
import org.apache.spark.{SparkConf, SparkContext}
object pagrank_test3 {
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("PageRankTest").setMaster("local[2]")
val sc = new SparkContext(conf)
val graph = GraphLoader.edgeListFile(sc,"E:\\Users\\11046\\IdeaProjects\\SparkFly\\followers.txt")
val ranks = graph.pageRank(0.001).vertices
val users = sc.textFile("E:\\Users\\11046\\IdeaProjects\\SparkFly\\users.txt").
map {
line =>
val fields = line.split(",")
(fields(0).toLong, fields(1))
}
val ranksByUsername = users.join(ranks).map {
case(id , (username,rank)) => (username,rank)
}
println(ranksByUsername.collect().mkString("\n"))
}
}
感谢:
https://blog.csdn.net/a1234h/article/details/77962536《Scala 中 var 和 val 的区别》
https://blog.csdn.net/mmake1994/article/details/79966145《Spark之GraphX案例-PageRank算法与分析》
https://www.jianshu.com/p/27d23bc29914《Spark实现PageRank算法》
5.自定义分区方式