RDD DataFrame DataSet

4 篇文章 0 订阅
1 篇文章 0 订阅

RDD DataFrame DataSet

https://databricks.com/wp-content/uploads/2016/06/Unified-Apache-Spark-2.0-API-1.png
https://www.infoq.cn/article/three-apache-spark-apis-rdds-dataframes-and-datasets
https://databricks.com/blog/2016/07/14/a-tale-of-three-apache-spark-apis-rdds-dataframes-and-datasets.html
http://dblab.xmu.edu.cn/blog/985-2/#more-985

RDD

  • 本质: Immutable的弹性可伸缩分布式对象集合(Resilient Distributed DataSet),可以分成多个区,每个分区为一个数据集片段,不同分区可以保存在集群不同节点上。Spark 计算操作的基本数据结构。
  • 两类操作:
    • 转换 Transformation: RDD -> RDD 惰性操作,延迟计算,粗粒度操作,反映RDD之间的相互依赖关系
      • map, flatMap, filter, groupByKey, union, join, …
    • 行动 Action:RDD -> 输出结果, 立即计算
      • reduce, count, collect, foreach, saveAs…
  • 创建方式:

读入外部数据源, sparkcontext parallelize方法,RDD -> RDD, DataFrame、Dataset -> RDD
- 读入外部数据源
- json, csv, hive, jdbc, odbc, mongo-spark-connector…
- 通过并行集合(数组)sparkcontext parallelize 创建RDD
- RDD -> RDD
- DataFrame/Dataset转RDD:
scala val rdd1=testDF.rdd val rdd2=testDS.rdd

```scala
// I 读取外部源
scala> val linesRDD = sc.textFile("hdfs://host:9000/test/word.txt")
// scala> val linesRDD = sc.textFile("/user/hadoop/word.txt")

// II sparkcontext(sc) parallelize 方法创建
scala>val list = List(1,2,3,4,5)
list: List[Int] = List(1, 2, 3, 4, 5)
scala>val rdd = sc.parallelize(list)    //rdd: org.apache.spark.rdd.RDD[Int]

// Again
scala> val list = List("Hadoop","Spark","Hive","Spark") //list: List[String] = List(Hadoop, Spark, Hive, Spark) 
scala> val rdd = sc.parallelize(list)   //rdd: org.apache.spark.rdd.RDD[String]  

// pairRDD
scala> val pairRDD = rdd.map(word => (word,1))  //pairRDD: org.apache.spark.rdd.RDD[(String, Int)]  
scala> pairRDD.foreach(print) // (Hadoop,1)(Spark,1)(Hive,1)(Spark,1)
scala> pairRDD.reduceByKey((a,b)=>a+b).foreach(print) //(Spark,2)(Hive,1)(Hadoop,1)
scala> pairRDD.groupByKey().foreach(print)    //(Spark,CompactBuffer(1, 1))(Hive,CompactBuffer(1))(Hadoop,CompactBuffer(1))
scala> pairRDD.keys
scala> pairRDD.values
scala> pairRDD.sortByKey()
scala> pairRDD.mapValues(x => x+1)
```

- 注意:
1. 如果使用了本地文件系统的路径,那么,必须要保证在所有的worker节点上,也都能够采用相同的路径访问到该文件,比如,可以把该文件拷贝到每个worker节点上,或者也可以使用网络挂载共享文件系统。
2. textFile()方法的输入参数,可以是文件名,也可以是目录,也可以是压缩文件等。比如,textFile(“/my/directory”), textFile(“/my/directory/*.txt”), and textFile(“/my/directory/*.gz”).
3. textFile()方法也可以接受第2个输入参数(可选),用来指定分区的数目。默认情况下,Spark会为HDFS的每个block创建一个分区(HDFS中每个block默认是128MB)。你也可以提供一个比block数量更大的值作为分区数目,但是,你不能提供一个小于block数量的值作为分区数目。
  • DAG(有向无环图):RDD 转换和行动等操作构成的依赖关系图
    RDD分区的依赖关系划分阶段

    • Job 作业,DAG解析时遇到Action操作即分隔为job.
    • Stage 阶段或任务的集合, 1 job = n stage, 1 stage = m tasks.
      • DAG中进行反向解析,遇到宽依赖就断开,切分成stage,遇到窄依赖就把当前的RDD加入到当前的stage中;将窄依赖尽量划分在同一个stage中,可以实现流水线计算.
    • Task 任务
    • 共享变量
      • 广播变量(只读): 阶段内任务见共享的公共数据,自动广播;跨越多个阶段的任务见共享数据, 手动广播, 在Executor中只保留一份;
      scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
      // broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0)
      scala> broadcastVar.value   //    res0: Array[Int] = Array(1, 2, 3)
      
      • 累加器: val accum = SparkContext.longAccumulator()或者SparkContext.doubleAccumulator()来创建,任务只能对其做累加,只有任务控制节点(Driver Program)可以使用value方法来读取累加器的值.
    • 宽窄依赖
      • 父RDD每一个分区只对应到子RDD的一个分区,为窄依赖(1to1, Mto1),否则宽依赖(1toN)。窄依赖适合分区间的并行计算。
  • 特点:

    • 数据只读,不可修改,如果需要修改数据,必须从父RDD转换到子RDD,由此在不同RDD之间建立了血缘关系。
    • 高效的容错和可用性:传统集群部分节点间采用数据复制等冗余容错,开销大。RDD通过父子依赖关系(粗粒度的转换操作)重新计算得到丢失的分区来实现容错,可并行进行。
    • 中间结果持久化到内存。
    • 存放的数据可以是Java对象,避免了不必要的对象序列化和反序列化开销。
  • 示例

    [root@localhost ~]# spark-shell
    Spark context Web UI available at http://host:4040
    Spark context available as 'sc' (master = spark://host:7077, app id = app-20190123110129-0006).
    Spark session available as 'spark'.
    Welcome to
        ____              __
        / __/__  ___ _____/ /__
        _\ \/ _ \/ _ `/ __/  '_/
    /___/ .__/\_,_/_/ /_/\_\   version 2.4.0
        /_/
    
    Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_191)
    Type in expressions to have them evaluated.
    Type :help for more information.
    
    scala> val textFile = spark.read.textFile("hdfs://host:9000/test/SparkReadMe.md")
    textFile: org.apache.spark.sql.Dataset[String] = [value: string]
    // tansformation 算子
    scala> val linesWithSpark = textFile.filter(line => line.contains("Spark"))
    linesWithSpark: org.apache.spark.sql.Dataset[String] = [value: string]
    // Action 算子
    scala> linesWithSpark.count()
    res0: Long = 20
    
    

DataFrame

DataFrame = RDD + Schema

  • 和RDD的区别。
    • RDD是分布式的 Java对象的集合,比如,RDD[Person]是以Person为类型参数,但是,Person类的内部结构对于RDD而言却是不可知的。
    • DataFrame是一种以RDD为基础的分布式数据集,也就是分布式的Row对象的集合(每个Row对象代表一行记录, 所有行的数据类型等Schema相同),提供了详细的结构信息,也就是我们经常说的模式(schema),Spark SQL可以清楚地知道该数据集中包含哪些列、每列的名称和类型。

DataFrame创建

外部数据源, RDD -> DataFrame, Dataset -> DataFrame

  • 读入外部数据源

    • json, csv, hive, jdbc, odbc, mongo-spark-connector…
    • Json Schema 自动推断:
      JSON 是一种可读性良好的重要结构化数据格式,许多原始数据往往以JSON的形式存在。然而JSON数据的体积却过于庞大,不利于批量数据分析。因此一个常见的数据处理步骤就是将JSON转换为ORC、Parquet等高效的列式存储格式。然而,不同版本的JSON数据往往具有不同的schema(例如新版本的Twitter API返回的数据可能比老版本的API返回的数据多出若干列)。人工合并整个JSON数据集所有记录的schema是一件十分枯燥繁琐的任务。Spark SQL在处理JSON数据时可以自动扫描整个数据集,得到所有记录中出现的数据列的全集,推导出完整的schema。(对于同名但不同类型的列,Spark SQL会尝试规约出一个公共类型。)
      示例:
    import spark.implicits._
    val spark=SparkSession.builder().getOrCreate()
    
    // support flat, non-flat, mixed, sparse type json
    val path = "src/main/resources/data/some.json"
    // {"name":"Michael"}
    // {"name":"Andy", "age":30}
    // {"name":"Justin", "age":19}
    
    val peopleDF = spark.read.format("json").option("inferSchema", "true").option("multiLine", true).load(path)
    // OR Equivalent to
    // val people = spark.read.json(path)
    
    // DF
    peopleDF.printSchema()
    peopleDF.select("name").show()
    peopleDF.select($"name", $"age" + 1).show()
    peopleDF.filter($"age" > 21).show()
    peopleDF.groupBy("age").count().show()
    peopleDF.createOrReplaceTempView("people")
    // sparkSQL
    spark.sql("SELECT * FROM people").show()
    


    上图展示了Spark SQL对三条不规整的个人信息JSON记录进行整理和schema推导的过程。第2条记录跟第1条记录类似,但多出了一个age字段,第3条与前两条也很类似,但是身高字段的类型是double而不是int。对此,Spark SQL的JSON数据源作出的处理是,将出现的所有列都纳入最终的schema中,对于名称相同但类型不同的列,取所有类型的公共父类型(例如int和 double的公共父类型为double)。通过这样的处理,我们最终就得到了右下方的DataFrame。

  • RDD -> DataFrame

    • 反射推断

      • 示例
      import spark.implicits._
      // case class for pattern match
      case class Person(name: String, age: Long)
      
      // people.txt
      // Michael, 29
      // Andy, 30
      // Justin, 19
      
      // Create an RDD of Person objects from a text file, convert it to a Dataframe
      val peopleRDD = spark.sparkContext
      .textFile("examples/src/main/resources/people.txt")
      .map(_.split(","))
      .map(attributes => Person(attributes(0), attributes(1).trim.toInt))
      // inference from peopleRDD 通过反射将类属性映射为DataFrame的Schema
      val peopleDF = peopleRDD.toDF()     // people.toDS() to dataset
      
      

      如果,RDD[T] 中 T 的类型不是 case class, 需要手动指明 DataFrame 的列名称:

      import spark.implicits._
      val rdd = spark.sparkContext
      .textFile("examples/src/main/resources/people.txt")
      .map(_.split(","))
      val peopleRDD = rdd.map {line=>
          (line._1,line._2.trim.toInt)}
      val peopleDF = peopleRDD.toDF("name","age")
      
    • 手动构造Schema, 然后应用于已知的的RDD上: RDD->rowRDD + strucType(StructFileds)->df

      • 示例
      import spark.implicits._
      // $example on:programmatic_schema$
      // Create an RDD
      val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")
      
      // The schema is encoded in a string
      val schemaString = "name age"
      
      // Generate the schema based on the string of schema
      val fields = schemaString.split(" ")
      .map(fieldName => StructField(fieldName, StringType, nullable = true))
      val schema = StructType(fields)
      
      // Convert records of the RDD (people) to Rows
      val rowRDD = peopleRDD
      .map(_.split(","))
      .map(attributes => Row(attributes(0), attributes(1).trim.toString))
      
      // Apply the schema to the RDD
      val peopleDF = spark.createDataFrame(rowRDD, schema)
      
    • Dataset -> DataFrame
      把case class封装成Row

    import spark.implicits._
    val testDF = testDS.toDF
    

Dataset

  • 结合了RDD DataFrame 的优点,类型安全(type-safe),面向对象(object-oriented)。
    • DataFrame 其实是 DataSet[Row]
    • 支持结构化和非结构化的数据
    • 因为序列化是通过Tungsten进行的,它使用了off heap数据序列化,不需要垃圾回收器来摧毁对象

Dataset 创建

基本的数据类型转换, RDD -> Dataset, DataFrame -> Dataset

  • 基本的数据类型转换, Encoders
    import spark.implicits._
    // $example on:create_ds$
    // Encoders are created for case classes
    val caseClassDS = Seq(Person("Andy", 32)).toDS()
    caseClassDS.show()
    // +----+---+
    // |name|age|
    // +----+---+
    // |Andy| 32|
    // +----+---+

    // Encoders for most common types are automatically provided by importing spark.implicits._
    val primitiveDS = Seq(1, 2, 3).toDS()
    primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)
  • RDD[T : case class] -> Dataset
val peopleDS = spark.sparkContext
    .textFile("examples/src/main/resources/people.txt")
    .map(_.split(","))
    .map(attributes => Person(attributes(0), attributes(1).trim.toInt)).toDS()
  • DataFrame -> Dataset
// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

转化:

RDD、DataFrame、Dataset三者有许多共性,有各自适用的场景常常需要在三者之间转换

  • DataFrame/Dataset转RDD:
    这个转换很简单
val rdd1=testDF.rdd
val rdd2=testDS.rdd
  • RDD转DataFrame:
import spark.implicits._
val testDF = rdd.map {line=>
      (line._1,line._2)
    }.toDF("col1","col2")

一般用元组把一行的数据写在一起,然后在toDF中指定字段名

  • RDD转Dataset:
import spark.implicits._
case class Coltest(col1:String,col2:Int)extends Serializable //定义字段名和类型
val testDS = rdd.map {line=>
      Coltest(line._1,line._2)
    }.toDS

可以注意到,定义每一行的类型(case class)时,已经给出了字段名和类型,后面只要往case class里面添加值即可

  • Dataset转DataFrame:
    这个也很简单,因为只是把case class封装成Row
import spark.implicits._
val testDF = testDS.toDF
  • DataFrame转Dataset:
import spark.implicits._
case class Coltest(col1:String,col2:Int)extends Serializable //定义字段名和类型
val testDS = testDF.as[Coltest]

这种方法就是在给出每一列的类型后,使用as方法,转成Dataset,这在数据类型是DataFrame又需要针对各个字段处理时极为方便

特别注意:
在使用一些特殊的操作时,一定要加上 import spark.implicits._ 不然toDF、toDS无法使用

<全文完>

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值