4.RDD的运算

RDD的运算

RDD运算的步骤

对于RDD的运算是以下一个步骤。

  1. 创建RDD对象
  2. 经过一系列的transformations对RDD进行转换
  3. 调用action触发RDD的计算(延迟计算)
  4. 要使用spark,开发者需要编写driver用于调度运行Worker
  5. RDD的运算也是一种类似于装饰者模式。通过控制抽象将逻辑传给对象。

RDD的创建

在Spark中创建RDD的方式可以分为3种:

  • 从集合创建RDD val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
  • 从外部存储创建RDD sc.textFile("c://a.txt")
  • 从其他RDD转换 val rdd2: RDD[Int] = rdd1.map(x => x)

RDD的转换

经过transformations对RDD进行转换,这些算子可以分为单value的和多value的,类似于

单value,都是窄连接

map(func)

类似于scala中map,对RDD中的每一个元素进行映射,窄连接.主要用于调整RDD中元素的数据结构。

 def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        // 默认的切片数为前面的核心数
        val rdd1: RDD[Int] = sc.parallelize(Array(1, 3, 4))
        rdd1.map(s=>{math.sqrt(s)}).collect().foreach(println)
    }
mapPartitions(func)

对每一个分区中的元素做映射,可能出现OOM,因为一次要将一个分区中的所有数据读入内存,并且只有当一个分区中的所有数据都处理完后才释放,极限情况下,内存会存两倍分区数的数据。出现OOM的概率变大。

 def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        // 默认的切片数为前面的核心数
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20))
        rdd1.mapPartitions(it=>it.map(math.pow(_,2))).collect().foreach(println)
        rdd1.map(
            // 建立到mysql的连接
            // 从mysql读数据
            // 一个rdd钟的每个元素操作时做一次操作
            t=>t
        )
        rdd1.mapPartitions(it=>{
                // 建立到mysql的连接
                // 从mysql读数据
            // 一个rdd钟的每个每个操作时做一次操作
            // 原来数据处理完才能释放原来的数据,导致oom
            // 效率比较高
            it
        })
    }
mapPartitionsWithIndex(func)

对每一个分区中的元素做一次映射,最外层的映射是从集合到集合,里面还有一个映射是对集合的操作,调用的map是scala中的map.

  def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        // 默认的切片数为前面的核心数
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20))
        rdd1.mapPartitions(it=>it.map(math.pow(_,2))).collect().foreach(println)
        rdd1.map(
            // 建立到mysql的连接
            // 从mysql读数据
            // 一个rdd钟的每个元素操作时做一次操作
            t=>t
        )
        rdd1.mapPartitions(it=>{
                // 建立到mysql的连接
                // 从mysql读数据
            // 一个rdd钟的每个每个操作时做一次操作
            // 原来数据处理完才能释放原来的数据,导致oom
            // 效率比较高
            it
        })
    }
flatMap(func)

类似于scala中的flatMap

 def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        // 默认的切片数为前面的核心数
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20))
        rdd1.flatMap(x=>Array(x,x)).collect().foreach(println)
        println("------------")
        // 使用flatMap实现过滤
        rdd1.flatMap{
            case x if x>50 =>Array(x)
            case _ => Array[Int]()
        }.collect().foreach(println)
        sc.stop()
    }
glom()

将一个分区中的元素用一个array存储

groupBy(func)

分区,注意的是分区需要shuffle性能低,二是分区之后每个区中间的数据的顺序不确定,其底层调用的依旧是groupBykey

def groupBy[K](f: T => K, p: Partitioner)(implicit kt: ClassTag[K], ord: Ordering[K] = null)
      : RDD[(K, Iterable[T])] = withScope {
    val cleanF = sc.clean(f)
    this.map(t => (cleanF(t), t)).groupByKey(p)
  }
    def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        //
        val rdd1: RDD[Int] = sc.parallelize(Array(3,5,70,60,10,20))
        // groupBy需要shuffle,分完组之后为之前的分区数。
        // shuffle会降低spark的性能,尽量不用
        rdd1.groupBy(x=>x%2).collect().foreach(println)
    }
filter(func)

类似于scala中的过滤器

sample()

抽样器,在大数据场景下可能需要对部分数据进行抽样运算。
withReplacement: Boolean, 抽样时是否放回
fraction: Double, 抽样的比例
seed 随机数生成种子
note:如果抽样时可放回则抽样比例可以大于1,如果不可放回则抽样比例应该在0到1之间。

def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,70,60,10,70,60,10,70,60,10,70,60,10,70,60,1070,60,10,70,60,10))
        // fraction 必须大于0小于1
        // withReplacement表示抽取后是否放回
        rdd1.sample(true,0.1).collect().foreach(println)
    }
distinct

对数据进行去重操作

  def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
        // 去重
        rdd1.distinct().collect.foreach(println)
        //用Ordering进行比较
    }

    
coalasce和repartition

将分区设置为指定数量,coalasce中含有一个是否shuffle的参数,该参数默认为false,在false的情形下,通过coalasce不能减少分区数,但是如果将shuffle设置为true,则可以将分区数进行增加。而这正式repartition的底层实现逻辑。

    def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
        coalesce(numPartitions, shuffle = true)
    }
  def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[3]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
        // 减少分区
        println(rdd1.getNumPartitions)
        println(rdd1.coalesce(2).getNumPartitions)
        // 默认没有shuffle,所以直接增加分区不行,会失败,
        println(rdd1.coalesce(4).getNumPartitions)
        // 如果要增加分区必须shuffle
        println(rdd1.coalesce(4,true).getNumPartitions)
        // 也可以使用repartition增加分区,底层调用的coalesce
        println(rdd1.repartition(4).getNumPartitions)

    }
sortBy

很难,又很简单的一个函数,用于排序,要通过shuffle.
简单用法,一重排序。

  def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
        // 降序
        rdd1.sortBy(x=>x,true).collect().foreach(println)
        // 升序
        rdd1.sortBy(x=>x,false).collect().foreach(println)
    }
def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val rdd1: RDD[String] = sc.parallelize(Array("aa","bb","abc","abcde"))

        rdd1.sortBy[(Int,String)](x=>(x.length,x),false).collect().foreach(println)
        val value: RDD[String] = rdd1.sortBy(x => (x.length, x))(Ordering.Tuple2(Ordering.Int.reverse, Ordering.String.reverse), ClassTag(classOf[(Int, String)]))
        value.collect().foreach(println)
    }
pipe

管道,针对每个分区,把 RDD 中的每个数据通过管道传递给shell命令或脚本,返回输出的RDD。一个分区执行一次这个命令. 如果只有一个分区, 则执行一次命令.

RDD的特殊玩法

flatMap过滤

双value

交集差集并集拉链

 def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
        val rdd2: RDD[Int] = sc.parallelize(Array(30,50,7,6,1,10,20,30,30))
        val rdd3: RDD[Int] = rdd1.union(rdd2)
        // 并集
        println("并集")
        rdd3.collect().foreach(println)
        (rdd1 ++ rdd2).collect.foreach(println)
        // 交集
        println("交集")
        rdd1.interp(rdd2).collect.foreach(println)
        // 差集,会将30减完
        println("差集")
        rdd1.subtract(rdd2).collect.foreach(println)
        // 拉链,分区数一定要一样,每个分区类的个数不一致
        println("拉链表")
       // rdd1.zip(rdd2).collect.foreach(println)
        rdd1.zipWithIndex().collect.foreach(println)
        // 只要求分区一致,利用scala的灵活
        println("灵活拉链表")
        rdd1.zipPartitions(rdd2)((it1,it2)=>{
            // 多余的扔掉
            it1.zip(it2)
            // it1和it2拉链,如果it1不够补-1,如果it2不够补-2
            it1.zipAll(it2,-1,-2)
        }).collect.foreach(println)
        // 笛卡尔积
        rdd1.cartesian(rdd2).collect().foreach(println)
    }

Keyvalue

key-value的RDD具有许多独有的操作算子,这些算子十分的常用。

分区逻辑
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
      (0 until numSlices).iterator.map { i =>
        val start = ((i * length) / numSlices).toInt
        val end = (((i + 1) * length) / numSlices).toInt
        (start, end)
      }
    }
partitionBy

// PairRDDFunctions RDD没有partitionBy,通过隐式转换为PairRDDFunctions新增功能 对数据进行重新分区,可以自定义分区器

object PartitionBy {
    def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        // rdd1为
        val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
        // RDD中隐式转换的设置将RDD转换后就有了reduceBykey的方法
      //  implicit def rddToPairRDDFunctions[K, V](rdd: RDD[(K, V)])
    //                                            (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null): PairRDDFunctions[K, V] = {
     //       new PairRDDFunctions(rdd)
  //      }
        val rdd2: RDD[(Int, Int)] = rdd1.map((_, 1))
        rdd2.partitionBy(new HashPartitioner(2){
            override def getPartition(key: Any): Int ={

            }
                
        }).glom().collect().foreach(arr=>println("a: "+arr.mkString(", ")))
    }
}
// 默认的hash分区器
  def numPartitions: Int = partitions

  def getPartition(key: Any): Int = key match {
    case null => 0
    case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
  }
    def nonNegativeMod(x: Int, mod: Int): Int = {
    val rawMod = x % mod
    rawMod + (if (rawMod < 0) mod else 0)
  }
reduceByKey

(K,V)->(K,V),分区内的运算和分区间的运算一致且没有初值,

def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        // rdd1为
        val rdd1: RDD[Int] = sc.parallelize(Array(30, 50, 70, 60, 10, 20, 30, 30))
        // 预聚合
        val rdd2: RDD[(Int, Int)] = rdd1.map((_, 1))
        rdd2.reduceByKey(_+_).collect().foreach(println)
    }

    
groupByKey

按照key进行分组, (key,compactBuffer(value,value,value))。groupByKey与reduceByKey相比,后者多一个预聚合的过程。原因是groupByKey在运行时伴随着shuffle

val rdd1 = sc.parallelize(List(("a",1),("b",1),("b",1),("c",1),("c",1),("b",1))).groupbyKey
// rdd1 (("a",compactBuffer(1)),("b",compactBuffer(1,1,1)),("c",compactBuffer(1,1)))
aggregateByKey

有三个参数,第一个参数是初值,第二个参数是分区内函数和分区间函数

def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)
        println("求最大值最小值")
            rdd1.aggregateByKey((Int.MinValue,Int.MaxValue))(
                {
                    case ((max,min),v) =>(max.max(v),min.min(v))
                },{
                    case  ((max,min),(max1,min1)) =>(max+max1,min+min1)
                }
            ).collect().foreach(println)
        // 求平均值
            rdd1.aggregateByKey((0,0))(
                {
                    case ((sum,count),v) => (sum+v,count+1)
                },
                {
                    case ((sum,count),(sum1,count1)) => (sum+sum1,count+count1)
                }
            ).map{
                case (k,(sum,count))=> (k,sum.toDouble/count)
            }.collect().foreach(println)
        // 求平均值2
            rdd1.aggregateByKey((0.0,0.0))(
                {
                    case ((avg,count),v) => ((avg*count+v)/(count+1),count+1)
                },
                {
                    case ((avg1,count1),(avg2,count2)) =>{
                        val d: Double = avg1*count1 + avg2*count2
                        val d1: Double = count1 + count2
                        (d/d1,d1)
                    }
                }
            ).collect().foreach(println)
    }
foldByKey

类似于aggregateByKey,只是分区内操作和分区间操作是一致的。

combineByKey

比aggregateByKey还要多一个参数,其优势在于初始值是可以运算出来的,需要加类型,其他不要加主要是有classTag,它可以在运行时推断类型

def combineByKey[C](
                       createCombiner: V => C,  // 创建初始规则
                       mergeValue: (C, V) => C,
                       mergeCombiners: (C, C) => C): RDD[(K, C)] = self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners,
        partitioner, mapSideCombine, serializer)(null)
}
input.combineByKey(
    (_, 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))
sortByKey

根据key来进行排序

object SortByKey {
    // 隐式转换Order
    implicit private val value: Ordering[User] = new Ordering[User] {
        override def compare(x: User, y: User): Int = x.age - y.age
    }
    // 对User按照年龄排序,需要重写Ordering
    def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val rdd1 = sc.parallelize(User(10,"d")::User(29,"e")::User(21,"a")::Nil)
        val rdd2 = rdd1.map((_, 1))

        rdd2.sortByKey().collect().foreach(println)

    }
}

case class User(age :Int,name: String)
mapValues

只对keyvalue中的value进行map

join

join默认为内连接,两个rdd都有的进行连接,leftOuterJoin左连接=>(v,option[v]),fullOuterJoin=>(option[v],option[v])

object Join {
    def main(args: Array[String]): Unit = {
        // 两个RDD之间的连接,按照key相等作为连接条件,只连接都有的
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        var rdd1 = sc.parallelize(Array((1, "a"), (1, "b"), (2, "c"),(4,"d")))
        var rdd2 = sc.parallelize(Array((1, "aa"), (3, "bb"), (2, "cc")))
        //按照key相等作为连接条件,只连接都有的 内连接
        val rdd3: RDD[(Int, (String, String))] = rdd1.join(rdd2)
        // 左连接,左边的全部和右边的相同的
        val rdd4: RDD[(Int, (String, Option[String]))] = rdd1.leftOuterJoin(rdd2)
        // 全外连接
        val rdd5: RDD[(Int, (Option[String], Option[String]))] = rdd1.fullOuterJoin(rdd2)
        rdd3.collect().foreach(println)
        rdd4.collect().foreach(println)
        rdd5.collect().foreach(println)
        sc.stop()
    }
}
cogroup

作用:在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD,

test
package cn.lpc.RDD.KeyValue

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/*
1.	数据结构:时间戳,省份,城市,用户,广告,字段使用空格分割。
1516609143867 6 7 64 16
1516609143869 9 4 75 18
1516609143869 1 7 87 12
统计一个省份广告点击排名前三
 */
object Practice {
    def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
        val sc = new SparkContext(conf)
        val originalData: RDD[String] = sc.textFile("c://agent.log")
        val proAdsAndOne: RDD[((String, String), Int)] = originalData.map(s => {
            val strings: Array[String] = s.split(" ")
            ((strings(1), strings(4)), 1)
        })
        val proAdsAndCount: RDD[((String, String), Int)] = proAdsAndOne.reduceByKey(_ + _)
        val proAndAdsCount: RDD[(String, Iterable[(String, Int)])] = proAdsAndCount.map {
            case ((pro, ads), count) => (pro, (ads, count))
        }.groupByKey()

        val result: RDD[(String, List[(String, Int)])] = proAndAdsCount.mapValues(
            it => it.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
        ).sortByKey()

        result.collect().foreach(println)

        // 一口气版本
       originalData.map(s => {
            val strings: Array[String] = s.split(" ")
            ((strings(1), strings(4)), 1)
        })
           .reduceByKey(_ + _).map {
            case ((pro, ads), count) => (pro, (ads, count))
        }
           .groupByKey().mapValues(
            t => t.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
        )
           .sortByKey()
           .collect()
           .foreach(println)

        // 统计点击用户数,而不是点击数该怎么做呢

        
        sc.stop()
    }
}

行动算子

RDD的转换算子在转换后会生成一个新的RDD,而行动算子的返回值不是RDD。这是两者的最大区别,只要传入的是func,最后都会送到driver端。

reduce(func)

通过func函数聚集RDD中的所有元素,

collect

再驱动程序中,以数组的形式返回数据集中的所有元素

count

计数

first

获取第一个分区的第一个元素

take(n)

取前n个元素

takeOrdered(n)

排序后取n个

aggregate(num)(func,func1)

与初值进行combine,然后聚合,注意的是aggregate,初值会计算n+1次,n个分区每个分区之内加一次,所有分区间计算时还会加上一次

fold(num)(func)

aggregate的简化操作,func和func1相同

saveAsTextFile

以文本形式存入文件系统之中

saveAsSequenceFile

SequenceFile是Hadoop专用的

saveAsObjectFile

用于将 RDD 中的元素序列化成对象,存储到文件中

countByKey()

针对(K,V)类型的 RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。

foreach(func)

对每个元素执行一次func,每个函数是在executor上执行,不是在driver执行,所以比较省内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值