Spark 读写数据、抽象转换 拾遗

作者: 利伊奥克儿 | 来源:发表于2019-07-22 22:49 被阅读0次

    package com.test.spark
    
    import org.apache.spark.sql.{Dataset, Row, SaveMode, SparkSession}
    
    /**
      * @author Administrator
      *         2019/7/22-17:09
      *
      */
    object TestReadData {
      val spark = SparkSession
        .builder()
        .appName("TestCreateDataset")
        .config("spark.some.config.option", "some-value")
        .master("local")
        .enableHiveSupport()
        .getOrCreate()
    
      def main(args: Array[String]): Unit = {
        testRead
      }
    
      def testRead(): Unit = {
        //  parquet 如果有损坏啥的容易莫名的错误
        val parquet: Dataset[Row] = spark.read.parquet("D:\\DATA-LG\\PUBLIC\\TYGQ\\INF\\PersonInfoCommon.parquet")
        parquet.show()
    
        // Spark SQL 的通用输入模式
        val commonRead: Dataset[Row] = spark.read.format("json").load("D:\\DATA-LG\\PUBLIC\\TYGQ\\INF\\testJson")
        commonRead.show()
        // Spark SQL 的通用输出模式
        commonRead.write.format("parquet").mode(SaveMode.Append).save("D:\\DATA-LG\\PUBLIC\\TYGQ\\INF\\PersonInfoCommon.parquet")
    
        // Spark SQL 的专业输入模式
        val professionalRead: Dataset[Row] = spark.read.json("D:\\DATA-LG\\PUBLIC\\TYGQ\\INF\\testJson")
        professionalRead.show()
        // Spark SQL 的专业输出模式
        professionalRead.write.mode(SaveMode.Append).parquet("D:\\DATA-LG\\PUBLIC\\TYGQ\\INF\\PersonInfoProfessional.parquet")
    
        val readParquet: Dataset[Row] = spark.sql("select * from parquet.`D:\\DATA-LG\\PUBLIC\\TYGQ\\INF\\PersonInfoCommon.parquet`")
        readParquet.show()
      }
    
    }
    
    //输出:
    +---+---------------+---------+
    |age|             ip|     name|
    +---+---------------+---------+
    | 24|    192.168.0.8|  lillcol|
    |100|  192.168.255.1|    adson|
    | 39|  192.143.255.1|     wuli|
    | 20|  192.168.255.1|       gu|
    | 15|  243.168.255.9|     ason|
    |  1|  108.168.255.1|   tianba|
    | 25|222.168.255.110|clearlove|
    | 30|222.168.255.110|clearlove|
    +---+---------------+---------+
    
    +---+---------------+---------+
    |age|             ip|     name|
    +---+---------------+---------+
    | 24|    192.168.0.8|  lillcol|
    |100|  192.168.255.1|    adson|
    | 39|  192.143.255.1|     wuli|
    | 20|  192.168.255.1|       gu|
    | 15|  243.168.255.9|     ason|
    |  1|  108.168.255.1|   tianba|
    | 25|222.168.255.110|clearlove|
    | 30|222.168.255.110|clearlove|
    +---+---------------+---------+
    
    +---+---------------+---------+
    |age|             ip|     name|
    +---+---------------+---------+
    | 24|    192.168.0.8|  lillcol|
    |100|  192.168.255.1|    adson|
    | 39|  192.143.255.1|     wuli|
    | 20|  192.168.255.1|       gu|
    | 15|  243.168.255.9|     ason|
    |  1|  108.168.255.1|   tianba|
    | 25|222.168.255.110|clearlove|
    | 30|222.168.255.110|clearlove|
    +---+---------------+---------+
    
    +---+---------------+---------+
    |age|             ip|     name|
    +---+---------------+---------+
    | 24|    192.168.0.8|  lillcol|
    |100|  192.168.255.1|    adson|
    | 39|  192.143.255.1|     wuli|
    | 20|  192.168.255.1|       gu|
    | 15|  243.168.255.9|     ason|
    |  1|  108.168.255.1|   tianba|
    | 25|222.168.255.110|clearlove|
    | 30|222.168.255.110|clearlove|
    | 24|    192.168.0.8|  lillcol|
    |100|  192.168.255.1|    adson|
    | 39|  192.143.255.1|     wuli|
    | 20|  192.168.255.1|       gu|
    | 15|  243.168.255.9|     ason|
    |  1|  108.168.255.1|   tianba|
    | 25|222.168.255.110|clearlove|
    | 30|222.168.255.110|clearlove|
    +---+---------------+---------+
    
    

    保存

    文件保存选项

    模式 注释
    Append DataFrame的内容将被追加到现有数据中。
    Overwrite 现有数据将被数据Daframe的内容覆盖。
    ErrorIfExists 如果数据已经存在,报错。
    Ignore 如果数据已经存在,不执行任何操作

    注:这些保存模式不使用任何锁定,不是原子操作。
    如果使用 Overwrite 同时该路径(path)又是数据源路径,要先对数据进行持久化操作,
    否则会在读取path之前将该数据删除掉,导致后续lazy 读取数据的时候报文件不存在的错误。


    类型之间的转换

    之前关于Spark 三中抽象之间的转换老是有些纠结
    现在对它们之间的转换做个总结

    在 SparkSQL 中 Spark 为我们提供了两个新的抽象,分别是 DataFrame 和 DataSet。
    他们和 RDD 有什啥关系呢?
    首先从版本的产生上来看:RDD(Spark1.0) —> DataFrame(Spark1.3) —> DataSet(Spark1.6)

    如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。
    不同是的他们的执行效率和执行方式。
    在后期的 Spark 版本中,DataSet 会逐步取代 RDD 和 DataFrame 成为唯一的 API 接口。
    所以后续开发 我更多的面向DataSet进行开发了。

    RDD
    1. RDD 弹性分布式数据集,Spark 计算的基石,为用户屏蔽了底层对数据的复杂抽象和处理,为用户提供了一组方便的数据转换与求值方法。
    2. RDD 是一个懒执行的不可变的可以支持 Lambda 表达式的并行数据集合。
    3. RDD 的最大好处就是简单,API 的人性化程度很高。
    4. RDD 的劣势是性能限制,它是一个 JVM 驻内存对象,这也就决定了存在 GC 的限制和数据增加时 Java 序列化成本的升高。
    DataFrame
    1. 与 RDD 类似,DataFrame 也是一个分布式数据容器。
    2. 然而 DataFrame 更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即 schema。
    3. 与 Hive 类似,DataFrame 也支持嵌套数据类型(struct、array 和 map)。
    4. 从 API 易用性的角度上看,DataFrame API 提供的是一套高层的关系操作,比函数式的 RDD API 要更加友好,门槛更低。
    5. 由于与 R 和 Pandas 的 DataFrame 类似,Spark DataFrame 很好地继承了传统单机数据分析的开发体验。

    Q: DataFrame性能上比 RDD 要高的原因:

    A:主要有两个原因

    1. 定制化内存管理
      数据以二进制的方式存在于非堆内存,节省了大量空间之外,还摆脱了 GC 的限制。
      DataFrame定制化内存管理.png
    2. 优化的执行计划
      查询计划通过 Spark catalyst optimiser 进行优化。
    DataSet
    1. 是 DataFrame API 的一个扩展,是 Spark 最新的数据抽象。
    2. 用户友好的 API 风格,既具有类型安全检查也具有 DataFrame 的查询优化特性。
    3. DataSet 支持编解码器,当需要访问非堆上的数据时可以避免反序列化整个对象,提高了效率。
    4. 样例类被用来在 DataSet 中定义数据的结构信息,样例类中每个属性的名称直接映射到 DataSet 中的字段名称。
    5. DataFrame 是 DataSet 的特列,type DataFrame = Dataset[Row] ,所以可以通过 as 方法将 DataFrame 转换为 DataSet。Row 是一个类型,跟 Car、Person 这些的类型一样,所有的表结构信息都用 Row 来表示。
    6. DataSet 是强类型的。比如可以有 Dataset[Car],Dataset[Person],
      DataFrame 只是知道字段,但是不知道字段的类型,所以在执行这些操作的时候是没办法在编译的时候检查是否类型失败的,比如你可以对一个 String 进行减法操作,在执行的时候才报错,
      而 DataSet 不仅仅知道字段,而且知道字段类型,所以有更严格的错误检查。
      就跟 JSON 对象和类对象之间的类比。

    三者之间的转换

    case class Person(name: String, age: Long) extends Serializable //case class的定义要在引用case class函数的外面。
    
    import spark.implicits._
    
    //类型之间的转换:注意输出类型
        def rddSetFrame() = {
        // 在使用一些特殊的操作时,一定要加上 import spark.implicits._ 不然 toDF、toDS 无法使用。
        val rdd: RDD[String] = spark.sparkContext.textFile("D:\\DATA-LG\\PUBLIC\\TYGQ\\INF\\testFile")
        val ds: Dataset[Row] = spark.read.json("D:\\DATA-LG\\PUBLIC\\TYGQ\\INF\\testJson")
        val df: DataFrame = rdd.map(_.split(",")).map(strArr => (strArr(0).trim(), strArr(1).trim().toInt)).toDF("nama", "age")
    
        //    rdd->df
        //一般用元组把一行的数据写在一起,然后在 toDF 中指定字段名。
        val rddTDf: DataFrame = rdd.map(_.split(",")).map(strArr => (strArr(0).trim(), strArr(1).trim().toInt)).toDF("nama", "age")
        //   df -> rdd
        val dfTRdd: RDD[Row] = df.rdd;
    
        //   rdd -> ds
        //定义每一行的类型 case class 时,已经给出了字段名和类型,后面只要往 case class 里面添加值即可。
        val rddTDs: Dataset[Person] = rdd.map(_.split(",")).map(strArr => Person(strArr(0).trim(), strArr(1).trim().toInt)).toDS()
        //   ds -> rdd
        val dsTRdd: RDD[Person] = rddTDs.rdd
    
        //    df->ds
        //这种方法就是在给出每一列的类型后,使用 as 方法,转成 DataSet,这在数据类型是 DataFrame 又需要针对各个字段处理时极为方便。
        val dfTDs: Dataset[Person] = df.as[Person]
        //    ds->df
        // 只是把 case class 封装成 Row。
        val dsTDf: DataFrame = ds.toDF
    
      }
    

    相关文章

      网友评论

        本文标题:Spark 读写数据、抽象转换 拾遗

        本文链接:https://www.haomeiwen.com/subject/rogglctx.html