Spark入门详解(三)-RDD编程

个人博客原文链接

RDD简介

RDD是弹性分布式数据集,完全弹性的,如果数据丢失一部分还可以重建。有自动容错、位置感知调度和可伸缩性。

RDD共包含两种计算方式,一种是transformations转换,一种是actions操作,每种计算方式包含一些常用的方法。
注:Transformations转换是Lazy的,也就是说从一个RDD转换生成另一个RDD的操作不是马上执行,Spark在遇到Transformations转换时只会记录需要这样的转换,并不会去执行,需要等到有Actions操作的时候才会真正启动计算过程进行计算。

RDD的特点

优点:

  • 编译时类型安全
  • 面向对象的编程风格

缺点:

  • 序列化和反序列化的性能开销
  • GC的性能开销

创建RDD对象

1、 读取外部文件
2、 并行一个集合

// 构建conf对象 SparkConf对象
// 必须设置的两个参数:运行模式,app的名字
val conf = new SparkConf().setMaster("local").setAppName("My Spark")
// 构建sc对象 SparkContext对象
val sc = SparkContext.getOrCreate(conf)

// 1.读取外部文件
// textFile objectFile sequenceFile hadoopFile newAPIHadoopFile
val text:RDD[String] = sc.textFile("src/data/fist.txt")
// 2.通过并行一个集合
val num:RDD[Int] = sc.parallelize(List(1,2,5,8,4))

RDD编程操作

转换操作(transformations)
object TestTransfer {
  def main(args: Array[String]): Unit = {
    // 设置日志级别
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc对象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)
    // 准备测试数据
    val num: RDD[Int] = sc.parallelize(1 to 10)
    val num1: RDD[Int] = sc.parallelize(List(1,5,3,2,5))
    val num2: RDD[Int] = sc.parallelize(List(2,7,3,5,1,0))
    val list: RDD[Int] = sc.parallelize(List(1,5,2,6,3,2))
    // 转化为PairRDD,(key,value)的形式
    val pairRdd: RDD[(Int, Int)] = list.map(x => (x,1))
    val pairRdd2: RDD[(Int, Int)] = list.map(x => (x,0))

    // 1. 普通的RDD
    // 2. PairRDD(键值对操作)
    // 我们在进行并行聚合、分组等操作时,常常需要利用键值对形式的RDD,称为Pair RDD
    // PairRDD相比于普通的RDD多了一些方法

    // 映射
    // 1. map(func) RDD
    // 作用于RDD的每一行,产出新RDD每一行为经过函数处理后的结果。
    list.map(_*2).foreach(println)

    // 2. flatMap(func) RDD
    // 展平元素,将每个符合条件的元素都放在一个集合中
    list.flatMap(Range(0,_)).foreach(println)

    // 过滤
    // filter(func) RDD
    // 将每个元素根据指定函数条件过滤
    list.filter(_ % 3 != 0).foreach(println)

    // 联合
    // union(other) RDD
    num1.union(num2).foreach(x => print(x + " "))

    // 交集
    // intersection(other) RDD
    num1.intersection(num2).foreach(x => print(x + " "))

    // 集合相减
    // subtract(other) RDD
    num1.subtract(num2).foreach(x => print(x + " "))
    
    // 去重复
    // distinct() RDD
    num.distinct().foreach(x => print(x + " "))
    
    // 聚合操作
    // 1. reduceByKey(func) PairRDD
    pairRdd.reduceByKey(_+_).foreach(println)

    // 2. combineByKey PairRDD
    // createCombiner: V => C ,这个函数把当前的值作为参数,此时我们可以对其做些附加操作(类型转换)并把它返回 (这一步类似于初始化操作)
    // mergeValue: (C, V) => C,该函数把元素V合并到之前的元素C(createCombiner)上 (这个操作在每个分区内进行)
    // mergeCombiners: (C, C) => C,该函数把2个元素C合并 (这个操作在不同分区间进行)
    val sum = pairRdd.combineByKey(v => (v,1),(a:(Int,Int),b:Int) => (a._1+b,a._2+1),(c1:(Int,Int),c2:(Int,Int)) => (c1._1+c2._1,c1._2+c2._2))
    val avg = sum.map{case(key,value)=>(key,value._1/value._2.toFloat)}
    avg.foreach(println)

    // 分组操作
    // 1. groupByKey()  PairRDD
    pairRdd.groupByKey().foreach(println)
    // 2. groupBy(func) RDD
    pairRdd.groupBy(x => x._1).foreach(println) // 根据key分组,相当于groupByKey
    pairRdd.groupBy(x => x._2).foreach(println) // 根据value分组

    // 排序
    // sortByKey(boolean) PairRDD
    // 默认true 升序 false 降序
    pairRdd.sortByKey().foreach(println)
    // sortBy(func,boolean) 自定义排序 RDD
    pairRdd.sortBy(x => x._2).foreach(println)

    // 连接操作
    // join(other) 等值连接 PairRDD
    val jnum: RDD[(Int, (Int, Int))] = pairRdd.join(pairRdd2)
    jnum.foreach(println)
    // rightOuterJoin(other) 右外连接 PairRDD
    val rnum: RDD[(Int, (Option[Int], Int))] = pairRdd.rightOuterJoin(pairRdd2)
    rnum.foreach(println)
    // leftOuterJoin(other) 左外连接 PairRDD
    val lnum: RDD[(Int, (Int, Option[Int]))] = pairRdd.leftOuterJoin(pairRdd2)
    lnum.foreach(println)
    // cogroup 全连接 PairRDD
    val cnum: RDD[(Int, (Iterable[Int], Iterable[Int]))] = pairRdd.cogroup(pairRdd2)
    cnum.foreach(println)
    // subtractByKey 根据key集合相减 PairRDD
    val snum: RDD[(Int, Int)] = pairRdd.subtractByKey(pairRdd2)
    snum.foreach(println)

    sc.stop()
  }
}

行动操作(actions)
object TestActions {
  def main(args: Array[String]): Unit = {
    // 设置日志级别
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc对象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)

    // 创建测试数据
    val num: RDD[Int] = sc.parallelize(1 to 10)

    // 1. collect()
    // 将rdd内容转化为本地集合,返回一个List集合
    // retrieve rdd contents as a local collection
    num.collect().foreach(println)

    // 2. take(n)
    // 返回前几个元素
    // return fist n elements
    num.take(3).foreach(println)

    // 3. top(n)
    // 返回最大的n个值
    num.top(3).foreach(println)

    // 4. takeOrdered(n)
    // 返回最小的n个值
    num.takeOrdered(3).foreach(println)

    // 5. takeSample(withReplace,num,[seed])
    // 随机采样(是否放回,采样数量,随机种子)
    num.takeSample(false,5).foreach(println)

    // 6. foreach(func)
    // RDD的每个元素会被func处理。在分区节点上运行,不会将结果收集到Drive节点
    num.foreach(println)

    // 7. count()
    // 返回RDD元素个数
    // count number of elements
    println(num.count())

    // 8. countByKey()
    // 统计PairRDD的key出现的次数
    val pairRdd = num.map(x => (x,1))
    println(pairRdd.countByKey())

    // 9. countByValue()
    // 统计RDD的value出现的次数
    println(num.countByValue())

    // 10. reduce(func)
    // 合并操作
    // merge elements with an associative function
    println(num.reduce(_+_))  // 计算总和

    // 11. fold(初始值)(func)
    // 带初始值的合并操作,每个分区计算一次+driver
    println(num.fold(10)(_+_))

    // 12. saveAsTextFile
    // 将RDD的元素输出到指定的外部存储介质中,如HDFS/AFS中
    num.saveAsTextFile("src/sql_out/text/te_1")

    sc.stop()
  }
}

RDD持久化方法

Spark通过cache()方法可以将RDD持久化到内存中,一旦首次被触发,该RDD将会被保留在计算节点的内存中,之后再调用这个RDD就不会再重复计算。

object TestActions {
  def main(args: Array[String]): Unit = {
    // 设置日志级别
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc对象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)

    // 创建测试数据
    val num: RDD[Int] = sc.parallelize(1 to 10)
    // 持久化到内存
    num.cache()

    sc.stop()
  }
 }

共享变量

多个task想要共享某个变量,Spark为此提供了两个共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)。

广播变量

Broadcast Variable会将使用到的变量,仅仅为每个节点拷贝一份,而不会为每个task都拷贝一份。当变量很大时,其最大的用处是优化性能,减少网络传输以及内存消耗。

  // 广播变量
  // 广播变量允许程序员将一个只读的变量缓存在每台机器上,而不用在任务之间传递变量。
  // 广播变量可被用于有效地给每个节点一个大输入数据集的副本
  // 注:为了确保所有的节点获得相同的变量,对象v在被广播之后就不应该再修改
  @Test
  def counter3()={
    // 设置日志级别
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc对象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)    // 小数据集
    val list = List(1,3,5,2,4,6)
    val rdd = sc.parallelize(List(1,3,2,3,4,5,6,1,3,4,5)).cache()
    // 广播变量
    val broadcast = sc.broadcast(list)
    rdd.filter(num => {
      // 使用到了匿名函数的非局部变量
      // 从广播变量中获取数据  就近原则 本地的worker
      val list = broadcast.value
      list.contains(num)
    }).foreach(println)
  }
累加变量

Accumulator可以让多个task共同操作一份变量,主要可以进行累加操作。但是task只能对Accumulator进行累加操作,不能读取它的值。只有Driver程序可以读取Accumulator的值。

  // 累加器
  // 累加器,提供了将工作节点中的值聚合到驱动器程序中的简单语法。
  // 累加器的一个常见用途是在调式时对作业执行过程中的事件进行计数。
  // 四种形式
  // a. sc.longAccumulator("累加器名字");
  // b. sc.doubleAccumulator("累加器名字");
  // c. sc.collectionAccumulator[T]("");
  // d. 自定义累加器 extends AccumulatorV2[T,T]
  @Test
  def counter1()={
    // 设置日志级别
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc对象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)
    // 累加器
    var countA = sc.longAccumulator("奇数个数")
    val list = List(1,3,5,2,4,6)
    // 计算rdd中所有的偶数和以及奇数个数
    val rdd = sc.parallelize(list)
    // 一次性计算两个值
    val result = rdd.filter(
      x => {
        // 注:累加器最好写在行动操作中
        // 如果写在转化操作中,后面每次行动操作都会调用之前转化操作中的累加器,造成数值错误
        // 解决方案:1 在第一次行动操作前先cache 2 避免写在转化操作中
        if (x%2 != 0) countA.add(1L) // 累加器
        x%2 == 0
      }
    ).reduce(_+_)

    println(result)
    println(countA.value) // 通过累加器的value属性获取到累加值为3
  }

  // 对于行动操作中的累加器,Spark只会把每个任务对各类假期的修改应用一次,无论失败还是重复计算时都绝对可靠。
  // 对于转化操作,可能会发生不止一次更新。
  // 解决方案:1 在第一次行动操作前先cache 2 避免写在转化操作中
  @Test
  def counter2()={
    // 设置日志级别
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc对象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)
    // 累加器
    var countA = sc.longAccumulator("奇数个数")
    val list = List(1,3,5,2,4,6)
    // 计算rdd中所有的偶数和以及奇数个数
    val rdd = sc.parallelize(list)
    // 一次性计算两个值
    val sum = rdd.filter(
      x => {
        if (x%2 != 0) countA.add(1L) // 累加器
        x%2 == 0
      }
    )
    // 没有行动操作
    println(countA.value)
    sum.cache().reduce(_+_) // 第一次调用行动操作之前先缓存cache
//    sum.reduce(_+_)
    // 有一个行动操作
    println(countA.value)
    sum.count()
    // 有两个行动操作
    println(countA.value)
  }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spark中的RDD(Resilient Distributed Datasets)是一种分布式的数据结构,它可以被分割成多个分区,每个分区可以在不同的节点上进行计算。RDD提供了一系列的转换和操作函数,可以对RDD进行各种操作。 RDD转换函数包括map、filter、flatMap、union、distinct、groupByKey、reduceByKey、sortByKey等。这些函数可以对RDD进行转换操作,生成新的RDD。 RDD操作函数包括count、collect、reduce、take、foreach等。这些函数可以对RDD进行操作,返回结果或者将结果输出到外部系统。 在使用RDD时,需要注意一些问题,如RDD的惰性计算、RDD的持久化、RDD的分区等。同时,还需要根据实际情况选择合适的RDD转换和操作函数,以达到最优的计算效果。 总之,Spark中的RDD转换和操作函数是非常重要的,掌握它们可以帮助我们更好地使用Spark进行分布式计算。 ### 回答2: Spark是一个基于内存计算的分布式计算框架,可以实现大规模数据集的快速处理。在Spark中,RDD(弹性分布式数据集)是数据处理的核心概念,它是一种可以分区、并行计算和容错的不可变数据结构。而Spark中的函数式编程模型则将RDD的转换与操作都看做是函数的调用,从而简洁明了,易于理解和操作。 在Spark中,解决一个具体问题通常涉及一系列RDD的转换和操作。RDD的转换包括对原有RDD进行过滤、映射、聚合等处理,得到新的RDD;操作则是对新的RDD进行输出、保存、统计、排序等操作。以下介绍几种常见的RDD转换和操作函数。 1. map函数 map函数是一种转换函数,它可以将一个RDD中每个元素通过一个用户定义的函数映射到另一个RDD中,并得到新的RDD。例如,将一个整型RDD中的每个元素都乘以2后得到一个新的整型RDD: ``` val rdd1 = sc.parallelize(Array(1, 2, 3, 4)) val rdd2 = rdd1.map(x => x*2) ``` 2. filter函数 filter函数也是一种转换函数,它可以根据用户定义的条件过滤一个RDD中的元素,并得到一个新的RDD。例如,将一个字符串RDD中长度大于5的元素过滤出来得到一个新的字符串RDD: ``` val rdd1 = sc.parallelize(Array("hello", "world", "spark", "rdd")) val rdd2 = rdd1.filter(x => x.length > 5) ``` 3. reduce函数 reduce函数是一种操作函数,它可以将一个RDD中的元素按照用户定义的函数进行聚合并得到一个结果。例如,将一个整型RDD中的所有元素相加得到一个整数结果: ``` val rdd1 = sc.parallelize(Array(1, 2, 3, 4)) val result = rdd1.reduce((x, y) => x + y) ``` 4. collect函数 collect函数也是一种操作函数,它可以将一个RDD中的所有元素收集起来并输出到Driver端。然而,使用collect函数需要注意RDD的大小,如果RDD很大,就可能会出现内存溢出的情况。例如,将一个整型RDD中的所有元素收集起来并输出到屏幕: ``` val rdd1 = sc.parallelize(Array(1, 2, 3, 4)) val result = rdd1.collect() result.foreach(println) ``` 5. saveAsTextFile函数 saveAsTextFile函数也是一种操作函数,它可以将一个RDD中的所有元素保存到指定的文本文件中。例如,将一个字符串RDD中的所有元素保存到hdfs的一个文本文件中: ``` val rdd1 = sc.parallelize(Array("hello", "world", "spark", "rdd")) rdd1.saveAsTextFile("hdfs://localhost:8020/user/abc/output") ``` 总之,Spark中的RDD转换和操作函数具有弹性、高效、简单等特点,能够满足各种大规模数据处理需求。需要特别注意的是,Spark中的函数式编程模型是基于JVM的,因此要充分利用内存和CPU资源,需要对集群配置和调优进行一定的优化和测试。 ### 回答3: Spark中的RDD(Resilient Distributed Datasets)是分布式的弹性数据集,它可以在大规模集群上并行化地计算,并且提供了一系列的转换和操作函数。其中,Spark提供的Spark函数简单易用,具有高效的数据处理能力,可以帮助开发者快速开发分布式应用程序。 RDD转换函数是将一个RDD转换成另一个RDD的函数,转换后的RDD通常包含了数据处理、筛选和过滤后的新数据集,可以用来接着进行后续的计算。 例如,map函数可以将RDD中的每个元素应用一个函数,然后返回一个新的转换过的RDD: ``` val originalData = sc.parallelize(List(1, 2, 3, 4, 5)) val mappedData = originalData.map(x => x * 2) ``` 这里,map函数将原始数据中的每个元素都乘上了2,返回了一个新的RDD。 除了map函数, 还有flatMap、filter、groupBy等常用的转换函数,都可以帮助我们对RDD做出各种各样的数据处理和转换。 RDD操作函数则是对RDD进行真正的计算操作,例如reduce、count、collect等函数,这些函数会触发Spark的分布式计算引擎执行真正的计算任务。 比如,reduce函数可以将RDD中的所有元素进行聚合,返回一个单一的结果: ``` val originalData = sc.parallelize(List(1, 2, 3, 4, 5)) val reducedData = originalData.reduce(_ + _) ``` 这里,reduce函数将原始数据中的所有元素进行相加操作,返回了一个整数类型的结果。 Spark提供的操作函数非常丰富,从基本的聚合、排序、统计操作,到高级的机器学习和图形处理等操作,开发者可以根据不同的业务需求灵活选择使用。 总之,Spark中的RDD转换和操作函数是分布式数据处理的核心之一,通过这些函数,开发者能够方便地对海量数据进行分布式的计算和处理。同时,Spark也提供了丰富的API和工具,便于开发者进行高效的Spark应用程序开发。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值