跟我一起学【Spark】之——数据分区

18 篇文章 0 订阅

前言

        控制数据分布以获得最少的网络传输可以极大地提升整体性能。

        如果给定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个迭代

hdfs版:

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")
  }
}

textfile版1: 

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"))
    }
  }

textfile版2: 

import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession

object pagerank_test4 {
  Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
  Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

  def main(args: Array[String]) {
    //在Spark 2.0中,我们可以通过SparkSession来实现同样的功能,而不需要显式地创建SparkConf, SparkContext 以及 SQLContext,
    // 因为这些对象已经封装在SparkSession中。使用生成器的设计模式(builder design pattern),如果我们没有创建SparkSession对象,则会实例化出一个新的SparkSession对象及其相关的上下文。
    val spark = SparkSession
      .builder
      .appName("SparkPageRank")
      .master("local[*]")
      .getOrCreate()
    //  val path="file:///E:\\Users\\11046\\IdeaProjects\\SparkFly\\followers.txt"
    val path = "E:\\Users\\11046\\IdeaProjects\\SparkFly\\pagerank5.txt"
    //val iters = if (args.length > 1) args(1).toInt else 10
    val iters = 10 // 迭代10次

    val lines = spark.read.textFile(path).rdd
    //获取每个页面的关联页面----去除重复投票
    val links = lines.map { s =>
      val parts = s.split("\\s+")
      (parts(0), parts(1))
    }.distinct()
    //按照每个页面聚合自己投票的url
    val link_group = links.groupByKey().cache()
    var ranks: RDD[(String, Double)] = link_group.mapValues(v => 1.0) //给每个页面初始得分都为1

    //迭代计算每个页面的得分值
    for (i <- 1 to iters) {
      val contribs = link_group.join(ranks).flatMap { case (id, (urls, rank)) =>
        val size = urls.size
        urls.map(url => (url, rank / size)) //计算出每个url被投票的得分
      }
      //计算每个url被投票了得多少分---key值聚合
      ranks = contribs.reduceByKey(_ + _).mapValues(0.15 + 0.85 * _)
    }
    ranks.collect().sortWith(_._2 > _._2).foreach(urls => println(urls._1 + "==>" + urls._2))
    spark.stop()
  }
}

 截至目前第四节,感谢:

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算法》

https://blog.csdn.net/u012102306/article/details/53121419《SparkSession简单介绍》

https://blog.csdn.net/qq_26442553/article/details/80300714《textfile,sequencefile和rcfile的使用与区别详解》

https://my.oschina.net/shea1992/blog/1922261《scala实现pageRank梳理+textRank生成文本摘要》

https://blog.csdn.net/bianenze/article/details/76269720《Spark下四种中文分词工具使用》

https://blog.csdn.net/zhuyu_deng/article/details/8800957《java中的matches()方法怎么用?在哪一个包中?》

https://www.bbsmax.com/A/Vx5M0QVYJN/《Spark Scala 读取GBK文件的方法》

5.自定义分区方式

       Spark提供的HashPartitionerRangePartitioner虽然可以满足大多数人用例。

       自定义分区实例:实现仅根据域名而不是整个URL来分区。

       思考过程:运用简单的哈希函数进行分区,http://www.cnn.com/WORLD 和 http://www.cnn.com/US
这两个网址可能被分到完全不同的节点上,但是这两个网址在同一个域名下更有可能相互连接,由于PageRank需要在每次迭代中从每个页面向它所有相邻的页面发送一条信息,因此把这些页面分组到同一个分区中会更好。

       技术实现:

继承org.apache.spark.Partitioner类
实现方法:
numPartitions:Int:返回创建出来的分区数
getPartition(key:Any):Int:返回给定键的分区编号(0到numPartitions-1)。
equals():Java判断相等性的标准方法。这个方法的实现非常重要,Spark需要用这个方法来检查你的分区器对象
是否和其他分区器实例相同,这样Spark才可以判断两个RDD的分区方式是否相同。

注意:确保getPartition()永远返回一个非负数

class DomainNamePartitioner(numParts:Int)extends Partitioner{
  override def numPartitions:Int = numParts
  override def getPartition(key: Any): Int = {
    val domain = new Java.net.URL(key.toString).getHost()
    val code = (domain.hashCode % numPartitions)
    if(code < 0 ){
      code + numPartitions
    }else{
      code
    }
  }
  override def equals(other:Any):Boolean = other match{
    case dnp:DomainNamePartitioner =>
      dnp.numPartitions == numPartitions
    case _=>
      false
  }
}

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值