1.PairRDD介绍

    Spark为包含键值对类型的RDD提供了一些专有的操作。这些RDD被称为PairRDD。PairRDD提供了并行操作各个键或跨节点重新进行数据分组的操作接口。例如,PairRDD提供了reduceByKey()方法,可以分别规约每个键对应的数据,还有join()方法,可以把两个RDD中键相同的元素组合在一起,合并为一个RDD。
2.创建Pair RDD
    程序示例:对一个英语单词组成的文本行,提取其中的第一个单词作为key,将整个句子作为value,建立 PairRDD
  1. val rdd=sc.parallelize(List("this is a test","how are you","do you love me","can you tell me"));
    //获取第一个单词作为键
    val words =rdd.map(x=>(x.split(" ")(0),x));
    words.collect().foreach(println);
输出结果:
(this,this is a test)
(how,how are you)
(do,do you love me)
(can,can you tell me)

3.PairRDD的转化操作
    PairRDD可以使用所有标准RDD上可用的转化操作。传递函数的规则也适用于PairRDD。由于PairRDD中包含二元组,所以需要传递的函数应当操作而元素而不是独立的元素。
                               PairRDD的相关转化操作如下表所示
针对两个PairRDD的转化操作 rdd={(1,2),(3,4),(3,6)} other={(3,9)}
函数名 目的 示例 结果
substractByKey 删掉RDD中键与other RDD
中的键相同的元素
rdd.subtractByKey(other) {(1,2)}
join 对两个RDD进行内连接
rdd.join(other) {(3,(4,9)),(3,(6,9))}
rightOuterJoin 对两个RDD进行连接操作,右外连接 rdd.rightOuterJoin(other) {(3,(4,9)),(3,(6,9))}
leftOuterJoin 对两个RDD进行连接操作,左外连接 rdd.rightOuterJoin(other) {(1,(2,None)),(3,(4,9)),(3,(6,9))}
cogroup 将两个RDD中拥有相同键的数据分组 rdd.cogroup(other) {1,([2],[]),(3,[4,6],[9])}
程序实例:
针对2 中程序生成的PairRDD,删选掉长度超过20个字符的行。
  1. val results=words.filter(value => value._2.length()<20);
    results.foreach(println)
    RDD上有fold(),combine(),reduce()等行动操作,pair RDD上则有相应的针对键的转化操作。
    (1)reduceByKey()与reduce()操作类似,它们都接收一个函数,并使用该函数对值进行合并。reduceByKey()会为数据集中的每个键进行并行的规约操作,每个规约操作会将键相同的值合并起来。reduceBykey()最终返回一个由各键规约出来的结果值组成的新的RDD。
程序示例:用reduceByKey实现单词计数
  1. val rdd=sc.parallelize(List("this is a test","how are you","do you love me","can you tell me"));
    val words =rdd.flatMap(line => line.split(" "));
    val results=words.map(word => (word,1)).reduceByKey( {case(x,y) => x+y});
    results.foreach(println)
输出:
(are,1)
(this,1)
(is,1)
(you,3)
(can,1)
(a,1)
(love,1)
(do,1)
(how,1)
(tell,1)
(me,2)
(test,1)

  (2)foldByKey()与fold()操作类似,他们都使用一个与RDD和合并函数中的数据类型相同的零值作为初始值。与fold()一样,foldByKey()操作所使用的合并函数对零值与另一个元素进行合并,结果仍为该元素。
    程序示例:求对应key的value之和
  1. val nums = sc.parallelize(List(Tuple2(1, 1), Tuple2(1, 3), Tuple2(2, 2), Tuple2(2, 8)));
    val results=nums.foldByKey(0)({case(x,y)=>x+y})
    results.collect().foreach(println)
结果:
(1,4)
(2,10)
(3)
    combineByKey()是最为常用的基于键进行聚合的函数。大多数基于键聚合的函数都是用它实现的。和aggregate()一样,combineByKey()可以让用户返回与输入数据类型不同的返回值。combineByKey()会遍历分区中的所有元素,因此,每个元素的键要么还么有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫做createCombiner()的函数来创建那个键对应的累加器的初始值。需要注意的是,这一过程会在每个分区中第一次出现每个键时发生,而不是在整个RDD中第一次出现一个键时发生。
    如果这是一个处理当前分区之前就已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并。
    由于每个分区都是独立处理的,因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应一个键的累加器,就需要使用用户提供的mergeCombiners()方法将各个分区的结果进行合并。
    以下程序示例使用combineBykey()求每个键对应的平均值。
  1. val nums = sc.parallelize(List(Tuple2(1, 1), Tuple2(1, 3), Tuple2(2, 2), Tuple2(2, 8)));
    val results=nums.combineByKey(
    (v)=>(v,1),
    (acc:(Int,Int),v) =>(acc._1+v,acc._2+1),
    (acc1:(Int,Int),acc2:(Int,Int))=>(acc1._1+acc2._1,acc1._2+acc2._2)
    ).map{case(key,value)=>(key,value._1/value._2.toFloat)}
    results.collectAsMap().map(println)
结果:
(2,5.0)
(1,2.0)
成功求出每个key对应value对应的平均值
*(4)并行度调优
    每个RDD都有固定数目的分区,分区数决定了在RDD上执行操作时的并行度。
    在执行聚合或者分组操作时,可以要求Spark使用给定的分区数。Spark始终尝试根据集群的大小推断出一个有意义的默认值,但是你可以通过对并行度进行调优来获得更好的性能表现。
    在Scala中,combineByKey()函数和reduceByKey()函数的最后一个可选的参数用于指定分区的数目,即numPartitions,使用如下:
  1. val results=nums.reduceByKey({(x,y) =>x+y},2);
5.数据分组
(1)groupByKey()
    groupByKey()会使用RDD中的键来对数据进行分组。对于一个由类型K的键和类型V的值组成的RDD,得到的RDD类型会是[K,Iterable[v]]。
    以下是程序示例,对PairRDD调用groupByKey()函数之后,返回的RDD类型是RDD[K,Iterable[v]]
  1. val nums = sc.parallelize(List(Tuple2(1, 1), Tuple2(1, 3), Tuple2(2, 2), Tuple2(2, 8)));
    val group=nums.groupByKey();
    val results=group.collect();
    for(value <- results){
    print(value._1+": ")
    for(elem <- value._2)
    print(elem+" ")
    println()

    }
输出结果:
1: 1 3 
2: 2 8 
(2)cogroup()
    除了对单个RDD的数据进行分组,还可以使用cogroup()函数对对个共享同一个键的RDD进行分组。对两个键的类型均为K而值得类型分别为V和W的RDD进行cogroup()时,得到结果的RDD类型为[(K,(Iterable[V],Iterable[W]))]。如果其中一个RDD对于另一个RDD中存在的某个键没有对应的记录,那么对应的迭代器则为空。
举例:
  1. val nums1 = sc.parallelize(List(Tuple2(1, 1), Tuple2(2, 2), Tuple2(1, 3),Tuple2(2, 4),Tuple2(3, 4)));
    val nums2 = sc.parallelize(List(Tuple2(1,1),Tuple2(1,3),Tuple2(2,3)))
    val results=nums1.cogroup(nums2)
    for(tuple2 <- results.collect()){
    print(tuple2._1+" [ ")
    for(it <- tuple2._2._1)
    print(it+" ")
    print("] [ ")
    for(it<-tuple2._2._2)
    print(it+" ")
    println("]")
    }
输出:
1 [ 1 3 ] [ 1 3 ]
3 [ 4 ] [ ]
2 [ 2 4 ] [ 3 ]
6.数据排序
在Scala中以字符串顺序对正数进行自定义排序
(1)对RDD进行排序:
  1. val nums =sc.parallelize(List(12,4,6,8,0,8));
    //隐式转换声明排序的依据
    implicit val sortIntegersByString = new Ordering[Int] {
    override def compare(x: Int, y: Int): Int = x.toString().compareTo(y.toString())
    }
    val results=nums.sortBy(value=>value);
    results.collect().foreach(println)
(2)对PairRDD,按key的值进行排序
  1. val nums = sc.parallelize(List(Tuple2(1, 1), Tuple2(2, 2), Tuple2(1, 3),Tuple2(2, 4),Tuple2(3, 4)));
    //隐式转换声明排序的依据
    implicit val sortIntegersByString = new Ordering[Int] {
    override def compare(x: Int, y: Int): Int = x.toString().compareTo(y.toString())
    }
    val results=nums.sortByKey();
    results.collect().foreach(println)
7.数据分区
(1)创建数据分区
    在分布式程序中,通信的代价很大,控制数据分布以获得最少的网络传输可以极大地提升整体性能。Spark程序可以通过控制RDD分区的方式来减少通信消耗。只有当数据集多次在诸如连接这种基于键的操作中,分区才会有作用
    Spark中所有的键值对RDD都可以进行分区。系统会根据一个针对键的函数对元素进行分组。Spark可以确保同一组的键出现在一个节点上。
    举个简单的例子,应用如下:内存中保存着很大的用户信息表,由(UserID,UserInfo[])组成的RDD,UserInfo是用户所订阅的所有主题列表。该应用会周期性地将这张表和一个小文件进行组合,这个小文件中存这过去5分钟发生的时间,其实就是一系列(UserId,LinkInfo)RDD,其中LinkInfo是用户访问的链接的主题。我们需要对用户访问其未订阅主题的页面情况进行统计。我们可以使用Spark的join()操作进行组合操作。将两者根据UserId连接之后,过滤出不在UserInfo[]中的LinkInfo,就是用户访问其未订阅主题的情况。
  1. val list1 =List(Tuple2("zhou",List("it","math")),Tuple2("gan",List("money","book")))
    val list2= List(Tuple2("zhou","it"),Tuple2("zhou","stock"),Tuple2("gan","money"),Tuple2("gan","book"))
    val userData =sc.parallelize(list1)
    val events = sc.parallelize(list2)
    val joined=userData.join(events)
    val results=joined.filter({
    case (id, (info, link)) =>
    !info.contains(link)
    }
    ).count()
    println(results)
输出:1
    这段代码可以正确运行,但是效率不高。因为每5分钟就要进行一次join()操作,而我们对数据集如何分区却一无所知。默认情况下,连接操作会将两个数据集中的所有键的哈希值都求出来,将该哈希值相同的记录通过网络传到同一台机器上,然后在那台机器上对所有键相同的记录进行连接操作。因为userData表比每5分钟出现的访问日志表events要大很多,所以要浪费时间进行额外的工作:在每次调用时都对userDAta表进行哈希值计算和跨节点数据混洗,虽然这些数据从来不会变化。
    要解决此问题:在程序开始的时候,对userData表进行partitionBy()转化操作,将这张表转化为哈希分区。可以通过向patitionBy传递一个spark.HashPartitioner对象来实现该操作。
    scala自定义分区方式:
  1. val list1 =List(Tuple2("zhou",List("it","math")),Tuple2("gan",List("money","book")))
    val list2= List(Tuple2("zhou","it"),Tuple2("zhou","stock"),Tuple2("gan","money"),Tuple2("gan","book"))
    val userData =sc.parallelize(list1).partitionBy(new HashPartitioner(100)).persist(StorageLevel.MEMORY_ONLY)
    这样以后在调用join()时,Spark就知道了该RDD是根据键的哈希值来分区的,这样在调用join()时,Spark就会利用这一点,只会对events进行数据混洗操作,将events中特定userId的记录发送到userData的对应分区所在的那台机器上。这样,需要网络传输的数据就大大减小了,程序运行的速度也显著提高。
    请注意,我们还对userData 这个RDD进行了持久化操作,默认情况下,每一个由转化操作得到的RDD都会在每次执行启动操作时重新计算生成,将userData持久化之后,就能保证userData能够在访问时被快速获取。
    *进一步解释数据分区带来的好处:
    如果没有将partitionBy()转化操作的结果进行持久化,那么后面每次用到这个RDD时都会重复对数据进行分区操作。不进行持久化会导致整个RDD谱系图重新求值。那样的话,partitionBy()带来的好处就会抵消,导致重复对数据进行分区以及跨节点的混洗,和没有指定分区方式时发生的情况是十分相似的。
(2)获取数据分区的方式
接(1)中程序:
  1. val list1 =List(Tuple2("zhou",List("it","math")),Tuple2("gan",List("money","book")))
    val list2= List(Tuple2("zhou","it"),Tuple2("zhou","stock"),Tuple2("gan","money"),Tuple2("gan","book"))
    val userData =sc.parallelize(list1).partitionBy(new HashPartitioner(100)).persist(StorageLevel.MEMORY_ONLY)
    println(userData.partitioner)
  RDD的属性partitioner就是存储了对应的分区方式
(3)从分区中获益的操作
    Spark中的很多操作都引入了根据键跨结点进行混洗的过程。所有这些操作都会从数据分区中获益。能够从数据分区中获益的操作有:groupWith(),join(),leftOuterJoin(),rightOuterJoin(),groupByKey(),reduceByKey(),combineByKey(),以及lockup()。
    对于像reduceByKey()这样只作用于单个RDD的操作,运行在未分区的RDD的时候或导致每个键所有对应值都在每台机器上进行本地计算,只需要把本地最终归约出的结果值从各工作节点传回主节点,所以原本的网络开销就不太大。而对于诸如cogroup()和join()这样的二元操作,预先进行数据分区会导致其中至少一个RDD(使用已知分区器的那个RDD)不发生数据混洗。如果两个RDD使用同样的分区方式,并且它们还缓存在同样的机器上(比如一个RDD是通过mapValues()从另一个RDD中创建出来的,这两个RDD就会拥有相同的键和分区方式),或者其中一个RDD还没有计算出来,那么跨节点数据混洗就不会发生了。
(4)影响分区方式的操作
    所有会为生成的结果RDD设好分区方式的操作:cogroup(),groupWith(),join(),leftOuterJoin(),rightOuterJoin(),groupByKey(),reduceByKey(),combineByKey(),partitionBy(),sort(),mapValues()(如果父RDD有分区方式的话),filter()(如果父RDD有分区方式的话)。其他所有操作生成的结果都不会存在特定的分区方式。
注意:    
    对于二元操作,输出数据的分区方式取决于父RDD的分区方式。默认情况下,结果会采用哈希分区,分区的数量和操作的并行度是一样的。如果其中一个父RDD已经设置过分区方式,那么结果就会采用那种分区方式;如果两个父RDD都设置过分区方式,结果RDD会采用第一个RDD的分区方式。
8.示例程序-PageRank
    PageRank算法是一种从RDD分区中获益的更复杂的算法,我们以它为例进行分析。PageRank算法用来根据外部文档指向一个文档的链接,对集合中每个文档的重要程度赋一个度量值。该算法可以用于对网页进行排序,当然,也可以用于排序科技文章或社交网络中有影响的用户。
    算法会维护两个数据集,一个由(pageID,linklist[])组成,包含每个页面的链接到的页面的列表;另一个由(pageID,rank)元素组成,包含每个页面的当前排序值。它按以下步骤进行计算:
    ① 将每个页面的排序值初始化为1.0
        ②在每次迭代中,向每个有直接链接的页面,发送一个值为rank(p)/numNeighbors(p)(出链数目)   的贡献量
        ③将每个页面的排序值设置为0.15+0.85*contributionsReceived
        最后两步会重复几个循环,在此过程中,算法会逐渐收敛于每个页面的实际PageRank值。在实际操作中,收敛通常需要进行十个迭代。
下面用Scala来实现PageRank算法:
  1. /*
    #以下是url的内容:
    www.baidu.com www.hao123.com
    www.baidu.com www.2345.com
    www.baidu.com www.zhouyang.com
    www.hao123.com www.baidu.com
    www.hao123.com www.zhouyang.com
    www.zhouyang.com www.baidu.com
    */
    val inputs =sc.textFile("C:\\url.txt")
    //url,[urls]
    val links =inputs.map(x=>(x.split(" ")(0),x.split(" ")(1)))
    .distinct()
    .groupByKey()
    .cache()
    //url,rank
    var ranks =links.mapValues(value =>1.0)
    for(i<-0 until 10){

    val contribs =links.join(ranks).flatMap({
    case(pageid,(links,rank))=>
    //url Double
    links.map(dest=>(dest,rank/links.size))
    })
    //reduce and add the contribs
    ranks=contribs.reduceByKey((x,y)=>x+y).mapValues(v => 0.15+0.85*v)
    }
    ranks.collect().foreach(println)
结果:
(www.hao123.com,0.3685546839262259)
(www.baidu.com,0.761571325242544)
(www.2345.com,0.3685546839262259)
(www.zhouyang.com,0.5269013026650011)
9.Scala设置自定义分区方式
    Spark允许你通过自定义Partitioner对象来控制RDD的分区方式,这样可以让你利用领域知识进一步减少通信消耗。
    举个例子,假设我们要在一个网页的集合上运行前一届中的PageRank算法。在这里,每个页面的ID是页面的URL。当我们使用简单的哈希函数进行分区时,拥有相似的URL的页面比如 http://www.baidu.com/news 与 http://www.baidu.com/map 可能被分在完全不同的节点上。但是我们知道,同一个域名下的网页更有可能相互连接。由于PageRank需要在每次迭代中从每个页面向它所有相邻的页面发送一条消息,因袭把这些页面分组在同一个分区中会更好。可以使用自定义的分区器来实现仅根据域名而不是整个URL进行分区。
    要实现先自定义Partitioner,需要继承Partitioner类并实现其下述方法:
    override def numPartitions: Int = ???
    返回创建的分区数量
    override def getPartition(key: Any): Int = ???
    返回给定键的数量
        override def equals(other:Any):Boolean = ???
    Spark需要这个方法来检查分区器对象是否与其他分区器实例相同,这样Spark才能判断两个RDD的分区方式是否相同。

  1. class DomainNamePartitioner (numParts:Int) extends Partitioner{
    override def numPartitions: Int = numParts
    //根据hashCode和numPartitions取余来得到Partition,因为返回的必须是非负数,所以对于hashCode为负的情况做了特殊处理
    override def getPartition(key: Any): Int = {
    val domain = new URL(key.toString).getHost();
    val code=(domain.hashCode%numPartitions)
    if(code<0){
    code+numPartitions
    }else{
    code
    }
    }

    override def equals(other:Any):Boolean = other match {
    //这个实例是DomainNamePartitioner的实例,并且numPartitions相同,返回true
    case dnp:DomainNamePartitioner =>
    dnp.numPartitions==numPartitions
    //否则,返回false
    case _ => false
    }
    }



















Spark中的键值对操作-scala的更多相关文章

  1. Spark中的键值对操作

    1.PairRDD介绍     Spark为包含键值对类型的RDD提供了一些专有的操作.这些RDD被称为PairRDD.PairRDD提供了并行操作各个键或跨节点重新进行数据分组的操作接口.例如,Pa ...

  2. Spark学习之键值对操作总结

    键值对 RDD 是 Spark 中许多操作所需要的常见数据类型.键值对 RDD 通常用来进行聚合计算.我们一般要先通过一些初始 ETL(抽取.转化.装载)操作来将数据转化为键值对形式.键值对 RDD ...

  3. Spark学习笔记——键值对操作

    键值对 RDD是 Spark 中许多操作所需要的常见数据类型 键值对 RDD 通常用来进行聚合计算.我们一般要先通过一些初始 ETL(抽取.转化.装载)操作来将数据转化为键值对形式. Spark 为包 ...

  4. Redis中的键值过期操作

    1.过期设置 Redis 中设置过期时间主要通过以下四种方式: expire key seconds:设置 key 在 n 秒后过期: pexpire key milliseconds:设置 key ...

  5. Redis源码解析:09redis数据库实现(键值对操作、键超时功能、键空间通知)

    本章对Redis服务器的数据库实现进行介绍,说明Redis数据库相关操作的实现,包括数据库中键值对的添加.删除.查看.更新等操作的实现:客户端切换数据库的实现:键超时相关功能的实现.键空间事件通知等. ...

  6. Spark学习笔记3:键值对操作

    键值对RDD通常用来进行聚合计算,Spark为包含键值对类型的RDD提供了一些专有的操作.这些RDD被称为pair RDD.pair RDD提供了并行操作各个键或跨节点重新进行数据分组的操作接口. S ...

  7. Spark学习之键值对(pair RDD)操作(3)

    Spark学习之键值对(pair RDD)操作(3) 1. 我们通常从一个RDD中提取某些字段(如代表事件时间.用户ID或者其他标识符的字段),并使用这些字段为pair RDD操作中的键. 2. 创建 ...

  8. spark入门(三)键值对操作

    1 简述 Spark为包含键值对类型的RDD提供了一些专有的操作.这些RDD被称为PairRDD. 2 创建PairRDD 2.1 在sprk中,很多存储键值对的数据在读取时直接返回由其键值对数据组成 ...

  9. Spark基础:(三)Spark 键值对操作

    1.pair RDD的简介 Spark为包含键值对类型的RDD提供了一些专有的操作,这些RDD就被称为pair RDD 那么如何创建pair RDD呢? 在不同的语言中有着不同的创建方式 在pytho ...

随机推荐

  1. kill -QUIT <pid>

    On Solaris and Linux a thread dump is also printed if the J2SE process receives a QUIT signal. So ki ...

  2. Dice Possibility

    Dice Possibility 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 What is possibility of rolling N dice and th ...

  3. linux操作命令实验

    实验内容:文件操作与用户操作实验 实验设备(环境):电脑.Vmware WorkStation 实验步骤: 一.创建新用户bob 目的:练习useradd命令 二.为新用户bob设置口令 目的:练习p ...

  4. 发布时去掉 debug 和 提醒日志,简单无侵入

    在 proguard 文件中加入下面代码,让发布时去掉 debug 和 提醒日志,简单无侵入! -assumenosideeffects class android.util.Log { public ...

  5. 转html5语义化标签总结一

    HTML 5的革新之一:语义化标签一节元素标签. 在HTML 5出来之前,我们用div来表示页面章节,但是这些div都没有实际意义.(即使我们用css样式的id和class形容这块内容的意义).这些标 ...

  6. ural1650 Billionaires

    Billionaires Time limit: 3.0 secondMemory limit: 64 MB You probably are aware that Moscow holds the ...

  7. Apache 隐藏入口文件 index.php

    新建 .htaccess文件至站点目录下,并写入如下代码: RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQ ...

  8. Android系统属性SystemProperties分析

    下面这几个博客总结的不错,有空看下: http://www.cnblogs.com/bastard/archive/2012/10/11/2720314.html http://blog.csdn.n ...

  9. javascript深入理解js闭包(看了挺多的,感觉这篇比较透彻)

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域 ...

  10. [Cocos2d-x]Lua 资源热更新

    什么是热更新 所谓的热更新,指的是客户端的更新. 大致的流程是,客户端在启动后访问更新的URL接口,根据更新接口的反馈,下载更新资源,然后使用新的资源启动客户端,或者直接使用新资源不重启客户端. 热更 ...