RDD编程

RDD是什么

弹性分布式数据集RDDSpark中不可变的分布式对象集合,每个RDD被分为多个分区,分区运行在集群不同节点上。我们可以通过Java、Scala、Python语言操作RDD,进行数据的处理。

RDD操作类型
  • 转化操作(transformation)

转化操作指将一个RDD转换成另一个RDD,就像我们将List转换成Map一样。

  • 行动操作(action)

行动操作指将RDD计算出一个结果,就像我们给List<Integer>求和一样。

RDD特性
  • 谱系图

RDD的谱系图会记录RDD的转化操作的记录,记录不同RDD间的依赖关系,这样在RDD部分数据丢失后,可以通过谱系图重新计算出丢失的RDD数据,保证了分区情况下数据的完整性。

下图所示:原RDD经过过滤生成两个新的RDD,两个新的RDD并集生成新的RDD,如果badRDD丢失,可以通过重新取errorsRDDwarningRDD的并集生成badRDD

graph TD
A[sourceRDD] -->|filter| B[errorsRDD]
A -->|filter| C[warningRDD]
B -->|union| D[badRDD]
C -->|union| D
  • 宽依赖与窄依赖

1、窄依赖:一个父RDD的分区对应于一个子RDD的分区或者多个父RDD的分区对应于一个子RDD的分区。

2、宽依赖:存在一个父RDD的一个分区对应一个子RDD的多个分区。

窄依赖是不存在数据混洗(shuffle)的场景的,宽依赖会产生数据混洗(shuffle)

Spark编程基础心得 spark编程基础pdf_自定义

  • 惰性求值

RDD的转化操作都是惰性的,只有在行动操作被调用时Spark才开始计算。这样的优势是Spark可以将一些操作合并到一起减少计算步骤。

RDD常用操作
  • 代码demo
def method(): Unit = {

    //初始化SparkContext
    val conf = new SparkConf().setAppName("demo").setMaster("local")
    val sc = new SparkContext(conf)

    //使用map()对rdd中数字求平方  1,4,9,16
    val list = sc.parallelize(List(1,2,3,4))
    list.persist()
    val result = list.map(x => x*x)
    println(result.collect().mkString(","))

    //使用flatMap()将rdd元素切分为多个元素  Hello,World,Scala
    val txt = sc.parallelize(List("Hello World","Scala"))
    val flatTxt = txt.flatMap(x => x.split(" "))
    println(flatTxt.collect().mkString(","))

    //使用filter()对rdd进行过滤  3,4
    println(list.filter(x => x>2).collect().mkString(","))

    //使用distinct()函数进行元素去重  B,A,C
    val total = sc.parallelize(List("A","B","C","A"))
    println(total.distinct().collect().mkString(","))

    //使用sample() 对rdd进行取样 返回指定部分的元素  结果是不确定的
    println(list.sample(false,0.4).collect().mkString(","))

    //使用union()将rdd进行合并    
    val a = sc.parallelize(List("A","B"))
    val b = sc.parallelize(List("B","C"))
    a.persist()
    b.persist()
    println(a.union(b).collect().mkString(",")) // A,B,B,C

    //使用intersection()求rdd的并集   B
    println(a.intersection(b).collect().mkString(","))

    //使用subtract() 返回只存在于第一个rdd不存在与第二个rdd的结果 A
    println(a.subtract(b).collect().mkString(","))

    //使用reduce()对rdd计算结果  10
    println(list.reduce((x,y) => x+y))

    //使用fold()对rdd进行有初始值的计算 每个分区调用时赋一次初始值  10
    println(list.fold(0)((x,y) => x+y))

    /*
      aggregate()求rdd的平均值
      第一个括号中是赋予初始值
      第二个括号是具体的逻辑  value表示每次拿到的值  acc表示(0,0)
      第三个表达式代表 将每个数字修改格式后的结果  (1,1),(2,1),(3,1),(3,1) 然后每个都相加
      最终结果为 avg : (9,4)
     */
    val nums = sc.parallelize(List(1,2,3,3))
    nums.persist()
    val avg = nums.aggregate((0,0))((acc, value) => (acc._1 + value, acc._2+1),
      ((acc1,acc2) => (acc1._1+acc2._1, acc2._2+acc2._2)))
    println(avg._1/avg._2.toDouble)

    //使用count()返回元素的个数  4
    println(nums.count())

    //使用countByValue()查看每个元素出现的次数  Map(1 -> 1, 3 -> 2, 2 -> 1)
    println(nums.countByValue())

    //使用take()从rdd中返回指定个数的元素  1,2
    println(nums.take(2).mkString(","))

    //使用top()从rdd中返回最前面的几个元素  3,3
    println(nums.top(2).mkString(","))

    /*
    使用takeOrdered()按照自定义的顺序返回结果

    class MyOrder extends Ordering[Int] {
      override def compare(a: Int, b: Int): Int = {
        return -(a-b);
      }
    }
	// 3,3
    nums.takeOrdered(2)(new MyOrder).mkString(",")

    */

    //使用foreach()遍历元素
    nums.foreach(println)

    nums.unpersist()
    a.unpersist()
    b.unpersist()
    list.unpersist()

    //pair RDD

    val init = sc.parallelize(List("1 2","3 4","3 6"))
    val kv = init.map(x => (x.split(" ")(0).toInt, x.split(" ")(1).toInt))
    kv.persist()

    //(1,2),(3,10) 合并具有相同key的值
    println(kv.reduceByKey((x, y) => x+y).collect().mkString(","))
    //(1,CompactBuffer(2)),(3,CompactBuffer(4, 6)) 相同key的值分组
    println(kv.groupByKey().collect().mkString(","))
    //(1,3),(3,5),(3,7) 对每个键值对的value进行操作
    println(kv.mapValues(x => x+1).collect().mkString(","))
    //(1,2),(1,3),(1,4),(1,5),(3,4),(3,5) 对每个键值对的value值比较生成新的键值对
    println(kv.flatMapValues(x => (x to 5)).collect().mkString(","))
    //1,3,3 获取全部的key
    println(kv.keys.collect().mkString(","))
    //2,4,6 获取全部的value
    println(kv.values.collect().mkString(","))
    //(1,2),(3,4),(3,6) 按照key值排序
    println(kv.sortByKey().collect().mkString(","))

    val initOther = sc.parallelize(List("3 9"))
    val other = initOther.map(x => (x.split(" ")(0).toInt, x.split(" ")(1).toInt))
    other.persist()
    //(1,2) 删除与other中相同key值的元素
    println(kv.subtractByKey(other).collect().mkString(","))
    //内连接 (3,(4,9)),(3,(6,9))
    println(kv.join(other).collect().mkString(","))
    //右外连接 (3,(Some(4),9)),(3,(Some(6),9))
    println(kv.rightOuterJoin(other).collect().mkString(","))
    //左外连接 (1,(2,None)),(3,(4,Some(9))),(3,(6,Some(9)))
    println(kv.leftOuterJoin(other).collect().mkString(","))
    //先将每个RDD中的相同key进行合并成list 然后再将两个RDD合并 没有的key值存空list (1,(CompactBuffer(2),CompactBuffer())),(3,(CompactBuffer(4, 6),CompactBuffer(9)))
    println(kv.cogroup(other).collect().mkString(","))

    //对每个key对应元素分别计数
    println(kv.countByKey())  //Map(1 -> 1, 3 -> 2)
    //将rdd的键值对转换为map
    println(kv.collectAsMap()) //Map(1 -> 2, 3 -> 6)
    //获取给定key值对应的所有值
    println(kv.lookup(3).mkString(","))  //4,6
    
    
    kv.unpersist()
    other.unpersist()

  }
  • combineByKey
//计算 每种新闻类型平均阅读数   每条数据  key:新闻类型 value:阅读数
scala> news.collect()
res41: Array[(String, Int)] = Array((toutiao,10), (zhihu,9), (tengxun,8), (xinlang,9), (toutiao,4), (xinlang,12), (zhihu,6), (tengxun,14))

scala> news.mapValues(x => (x,1)).reduceByKey((x,y) => (x._1 + y._1,x._2+y._2)).map{case(name, (read,times)) => (name,read/times)}.collect()
res42: Array[(String, Int)] = Array((zhihu,7), (tengxun,11), (toutiao,7), (xinlang,10))


//如上的操做可以通过 combineKey的api代替实现
news.combineByKey((v) => (v,1) , //新元素 createCombine
                  (acc: (Int, Int), v) => (acc._1+v, acc._2+1) ,//存在的元素mergeCombine 
                         (acc1: (Int, Int), acc2: (Int, Int)) => 
                         (acc1._1+acc2._1, acc1._2+acc2._2)) //多个分片合并
.map{case(key, value) => (key, value._1/value._2.toFloat)}.collect()//计算平均数
res8: Array[(String, Float)] = Array((zhihu,7.5), (tengxun,11.0), (toutiao,7.0), (xinlang,10.5))
RDD持久化
  • 当我们多次使用同一个RDD,可以将RDD进行持久化,默认情况下缓存在JVM堆内存中

级别

使用空间

CPU时间

是否在内存

是否在磁盘

MEMORY_ONLY





MEMORY_ONLY_SER





MEMORY_AND_DISK



部分

部分

MEMORY_AND_DISK_SER



部分

部分

DISK_ONLY





  • MEMORY_AND_DISK:内存数据放不下、写到磁盘。
  • MEMORY_AND_DISK_SER:内存数据放不下,写到磁盘,内存数据序列化。
RDD数据分区
  • 降低节点通讯成本

在分布式程序中、不同分区节点间的通讯代价是巨大的。当我们多次对同一个RDD进行数据处理时,需要对RDD进行预分区处理,保证处理RDD数据时,尽量避免跨节点数据混洗。对于处理两个pair RDD的场景时,根据key值进行分区,会使得网络通讯大大减少。

  • 举例:A.join(B)A.partitionBy(new Partitioner()).join(B)

默认情况下、连接操作会将两个数据集所有键的哈希值求出来,将哈希值相同的记录通过网络传到同一台机器上,然后计算,最后进行结果汇总。这会对两个RDD都产生数据混洗的结果。效率很低。

如果先将A进行数据分区,则只会对B进行哈希求值,然后把key值对应的数据传送到A预先分区好的位置。大大的减少了网络通讯代价,提高了效率。

  • 自定义分区算法与预处理RDD分区
//自定义的分区算法
class MyPartitioner() extends Partitioner {
  //获取分区数方法
  override def numPartitions: Int = 2

  //根据key值获取当前数据分区位置
  override def getPartition(key: Any): Int = {
    val location = key.toString.toInt % 2
    if(location >= 0) {
      return location
    } else {
      return -location
    }
  }
  
  //判断当前分区算法是否是自定义的分区算法
  override def equals(obj: Any): Boolean = {
    obj match {
      case mp : MyPartitioner =>
        return true
      case _ =>
        return false
    }
  }

}

def main(args: Array[String]): Unit = {

  val conf = new SparkConf().setMaster("local").setAppName("SparkDemo")
  val sc = new SparkContext(conf)

  //创建带有自定义分区的RDD
  val rdd = sc.parallelize(List("1 world","2 spark","3 sharding","4 watermelon","5 pineapple")).map(x => (x.split(" ")(0), x.split(" ")(1)))
    .partitionBy(new MyPartitioner()).persist()

  println("partitioner: "+rdd.partitioner + " size: "+rdd.partitioner.size)

  rdd.unpersist()

  sc.stop()

}