Spark SQL 快速入门系列(四)SparkSQL的数据读写

数据读写

目标

1,理解外部数据源的访问框架
2,掌握常见的数据源读写方式

初识 DataFrameReader

目标

理解 DataFrameReader 的整体结构和组成

SparkSQL 的一个非常重要的目标就是完善数据读取, 所以 SparkSQL 中增加了一个新的框架, 专门用于读取外部数据源, 叫做 DataFrameReader

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.DataFrameReader

val spark: SparkSession = ...

val reader: DataFrameReader = spark.read

DataFrameReader 由如下几个组件组成

组件解释
schema结构信息, 因为 Dataset 是有结构的, 所以在读取数据的时候, 就需要有 Schema 信息, 有可能是从外部数据源获取的, 也有可能是指定的
option连接外部数据源的参数, 例如 JDBC 的 URL, 或者读取 CSV 文件是否引入 Header 等
format外部数据源的格式, 例如 csv, jdbc, json 等

DataFrameReader 有两种访问方式, 一种是使用 load 方法加载, 使用 format 指定加载格式, 还有一种是使用封装方法, 类似 csv, json, jdbc 等

 @Test
  def reader2(): Unit ={
    Logger.getLogger("org").setLevel(Level.ERROR)
    //1. 创建 SparkSession
    val spark =  SparkSession.builder()
      .master("local[6]")
      .appName(this.getClass.getSimpleName)
      .getOrCreate()

    //2, 第一种形式
    spark.read
      .format("csv")
      .option("header",value = true)
      .option("inferSchema",value = true)
      .load("E:\\Project\\Spark\\spark-sql\\input\\BeijingPM20100101_20151231.csv")
      .show(10)

    println("---------第一种第二种读取分界线--------------")


    //3,第二种形式
    spark.read
      .option("header",value = true)
      .option("inferSchema",value = true)
      .csv("E:\\Project\\Spark\\spark-sql\\input\\BeijingPM20100101_20151231.csv")
      .show()

  }

但是其实这两种方式本质上一样, 因为类似 csv 这样的方式只是 load 的封装

在这里插入图片描述
注意:

  • 如果使用 load 方法加载数据, 但是没有指定 format 的话, 默认是按照 Parquet 文件格式读取
  • 也就是说, SparkSQL 默认的读取格式是 Parquet

总结

1,使用 spark.read 可以获取 SparkSQL 中的外部数据源访问框架 DataFrameReader
2,DataFrameReader 有三个组件 format, schema, option
3,DataFrameReader 有两种使用方式, 一种是使用 load 加 format 指定格式, 还有一种是使用封装方法 csv, json 等

初识 DataFrameWriter

目标

理解 DataFrameWriter 的结构

对于 ETL 来说, 数据保存和数据读取一样重要, 所以 SparkSQL 中增加了一个新的数据写入框架, 叫做 DataFrameWriter

val spark: SparkSession = ...

val df = spark.read
      .option("header", true)
      .csv("dataset/BeijingPM20100101_20151231.csv")

val writer: DataFrameWriter[Row] = df.write

DataFrameWriter 中由如下几个部分组成

组件解释
source写入目标, 文件格式等, 通过 format 方法设定
mode写入模式, 例如一张表已经存在, 如果通过 DataFrameWriter 向这张表中写入数据, 是覆盖表呢, 还是向表中追加呢? 通过 mode 方法设定
extraOptions外部参数, 例如 JDBC 的 URL, 通过 options, option 设定
partitioningColumns类似 Hive 的分区, 保存表的时候使用, 这个地方的分区不是 RDD 的分区, 而是文件的分区, 或者表的分区, 通过 partitionBy 设定
bucketColumnNames类似 Hive 的分桶, 保存表的时候使用, 通过 bucketBy 设定
sortColumnNames用于排序的列, 通过 sortBy 设定

mode 指定了写入模式, 例如覆盖原数据集, 或者向原数据集合中尾部添加等

Scala 对象表示字符串表示解释
SaveMode.ErrorIfExists“error”将 DataFrame 保存到 source 时, 如果目标已经存在, 则报错
SaveMode.Append“append”将 DataFrame 保存到 source 时, 如果目标已经存在, 则添加到文件或者 Table 中
SaveMode.Overwrite“overwrite”将 DataFrame 保存到 source 时, 如果目标已经存在, 则使用 DataFrame 中的数据完全覆盖目标
SaveMode.Ignore“ignore”将 DataFrame 保存到 source 时, 如果目标已经存在, 则不会保存 DataFrame 数据, 并且也不修改目标数据集, 类似于 CREATE TABLE IF NOT EXISTS

DataFrameWriter 也有两种使用方式, 一种是使用 format 配合 save, 还有一种是使用封装方法, 例如 csv, json, saveAsTable 等

第一种写法:

val spark: SparkSession = ...

val df = spark.read
  .option("header", true)
  .csv("dataset/BeijingPM20100101_20151231.csv")

// 使用 save 保存, 使用 format 设置文件格式
df.write.format("json").save("dataset/beijingPM")

// 使用 json 保存, 因为方法是 json, 所以隐含的 format 是 json
df.write.json("dataset/beijingPM1")

第二种写法:

@Test
  def write1(): Unit = {
    Logger.getLogger("org").setLevel(Level.ERROR)
    //1. 创建 SparkSession
    val spark = SparkSession.builder()
      .master("local[6]")
      .appName(this.getClass.getSimpleName)
      .getOrCreate()


    //2.读取数据集
    val df: DataFrame = spark.read.option("header", value = true).csv("E:\\Project\\Spark\\spark-sql\\input\\BeijingPM20100101_20151231.csv")

    //3.写入数据集 (第一种)
    df.write.json("E:\\Project\\Spark\\spark-sql\\output\\beijing_pm.json")

    //第二种
    df.write.format("json").save("E:\\Project\\Spark\\spark-sql\\output\\beijing_pm2.json")



  }

默认没有指定 format, 默认的 format 是 Parquet

总结

1,类似 DataFrameReader, Writer 中也有 format, options, 另外 schema 是包含在 DataFrame 中的
2,DataFrameWriter 中还有一个很重要的概念叫做 mode, 指定写入模式, 如果目标集合已经存在时的行为
3,DataFrameWriter 可以将数据保存到 Hive 表中, 所以也可以指定分区和分桶信息

读写 Parquet 格式文件

目标

1,理解 Spark 读写 Parquet 文件的语法
2,理解 Spark 读写 Parquet 文件的时候对于分区的处理

什么时候会用到 Parquet ?

在这里插入图片描述
一般处理数据都差不多是ETL的过程

E -> 抽取
T -> 处理,转换
L -> 装载,罗底

在 ETL 中, Spark 经常扮演 T 的职务, 也就是进行数据清洗和数据转换.

为了能够保存比较复杂的数据, 并且保证性能和压缩率, 通常使用 Parquet 是一个比较不错的选择.

所以外部系统收集过来的数据, 有可能会使用 Parquet, 而 Spark 进行读取和转换的时候, 就需要支持对 Parquet 格式的文件的支持.

使用代码读写 Parquet 文件

class ParquetTest {

  Logger.getLogger("org").setLevel(Level.ERROR)
  val spark: SparkSession = SparkSession.builder()
    .appName(this.getClass.getSimpleName)
    .master("local[6]")
    .getOrCreate()

  @Test
  def parquet1(): Unit ={
    //读取csv 文件数据
   val df = spark.read.option("header",value = true)
      .csv("E:\\Project\\Spark\\spark-sql\\input\\BeijingPM20100101_20151231.csv")

    //把数据写成parquet的形式
    df.write
      //设置类型
      .format("parquet")
      //看mode源码
      .mode(SaveMode.Overwrite)
      .save("E:\\Project\\Spark\\spark-sql\\output\\beijing_pm3")



    /**
     *  覆盖,追加,忽略,报错
     * def mode(saveMode: String): DataFrameWriter[T] = {
     *     this.mode = saveMode.toLowerCase(Locale.ROOT) match {
     * case "overwrite" => SaveMode.Overwrite
     * case "append" => SaveMode.Append
     * case "ignore" => SaveMode.Ignore
     * case "error" | "errorifexists" | "default" => SaveMode.ErrorIfExists
     * case _ => throw new IllegalArgumentException(s"Unknown save mode: $saveMode. " +
     * "Accepted save modes are 'overwrite', 'append', 'ignore', 'error', 'errorifexists'.")
     * }
     * this
     * }
     */



    //读取parquet 格式文件
    /**
     * 默认格式是否是parquet? -> 是
     * 是否可能读取文件夹呢? -> 是
     */

    spark.read
      .load("output\\beijing_pm3")
      .show()

  }
}

写入 Parquet 的时候可以指定分区

Spark 在写入文件的时候是支持分区的, 可以像 Hive 一样设置某个列为分区列

  /**
   * 表分区的概念,不仅在Parquet 上有,其他格式的文件也可以指定表分区
   */
  @Test
  def parquet2(): Unit ={

    //1,读取数据
   val df =  spark.read
      .option("header",value = true)
      .csv("E:\\Project\\Spark\\spark-sql\\input\\BeijingPM20100101_20151231.csv")

    //2,写文件,表分区
    df.write.partitionBy("year","month")
      .save("output\\beijing_pm4")

    //3,读文件,自动发现分区
    // 写分区表的时候,分区列不会包含在生成的文件中
    // 直接通过文件来进行读取的话,分区信息会丢失
    // SparkSQL 会进行自动的分区发现
    spark.read
      .parquet("output\\beijing_pm4\\year=2010\\month=5")
      .printSchema()
  }

结果如下:

在这里插入图片描述
这个地方指的分区是类似 Hive 中表分区的概念, 而不是 RDD 分布式分区的含义

分区发现

在读取常见文件格式的时候, Spark 会自动的进行分区发现, 分区自动发现的时候, 会将文件名中的分区信息当作一列. 例如 如果按照性别分区, 那么一般会生成两个文件夹 gender=male 和 gender=female, 那么在使用 Spark 读取的时候, 会自动发现这个分区信息, 并且当作列放入创建的 DataFrame 中

使用代码证明这件事可以有两个步骤, 第一步先读取某个分区的单独一个文件并打印其 Schema 信息, 第二步读取整个数据集所有分区并打印 Schema 信息, 和第一步做比较就可以确定

val spark = ...

val partDF = spark.read.load("dataset/beijing_pm/year=2010/month=1") 
partDF.printSchema()

把分区的数据集中的某一个区单做一整个数据集读取, 没有分区信息, 自然也不会进行分区发现

在这里插入图片描述

val df = spark.read.load("dataset/beijing_pm") 
df.printSchema()

此处读取的是整个数据集, 会进行分区发现, DataFrame 中会包含分去列

在这里插入图片描述

Table 1. SparkSession 中有关 Parquet 的配置

配置默认值含义
spark.sql.parquet.binaryAsStringfalse一些其他 Parquet 生产系统, 不区分字符串类型和二进制类型, 该配置告诉 SparkSQL 将二进制数据解释为字符串以提供与这些系统的兼容性
spark.sql.parquet.int96AsTimestamptrue一些其他 Parquet 生产系统, 将 Timestamp 存为 INT96, 该配置告诉 SparkSQL 将 INT96 解析为 Timestamp
spark.sql.parquet.cacheMetadatatrue打开 Parquet 元数据的缓存, 可以加快查询静态数据
spark.sql.parquet.compression.codecsnappy压缩方式, 可选 uncompressed, snappy, gzip, lzo
spark.sql.parquet.mergeSchemafalse当为 true 时, Parquet 数据源会合并从所有数据文件收集的 Schemas 和数据, 因为这个操作开销比较大, 所以默认关闭
spark.sql.optimizer.metadataOnlytrue如果为 true, 会通过原信息来生成分区列, 如果为 false 则就是通过扫描整个数据集来确定

总结

1,Spark 不指定 format 的时候默认就是按照 Parquet 的格式解析文件
2,Spark 在读取 Parquet 文件的时候会自动的发现 Parquet 的分区和分区字段
3,Spark 在写入 Parquet 文件的时候如果设置了分区字段, 会自动的按照分区存储

读写 JSON 格式文件

目标

1,理解 JSON 的使用场景
2,能够使用 Spark 读取处理 JSON 格式文件

什么时候会用到 JSON ?

在这里插入图片描述在 ETL 中, Spark 经常扮演 T 的职务, 也就是进行数据清洗和数据转换.

在业务系统中, JSON 是一个非常常见的数据格式, 在前后端交互的时候也往往会使用 JSON, 所以从业务系统获取的数据很大可能性是使用 JSON 格式, 所以就需要 Spark 能够支持 JSON 格式文件的读取

读写 JSON 文件

将要 Dataset 保存为 JSON 格式的文件比较简单, 是 DataFrameWriter 的一个常规使用

class JSONTest {

  Logger.getLogger("org").setLevel(Level.ERROR)
  val spark: SparkSession = SparkSession.builder()
    .appName(this.getClass.getSimpleName)
    .master("local[6]")
    .getOrCreate()

  @Test
  def json(): Unit ={

    val df = spark.read.option("header",value = true)
      .csv("E:\\Project\\Spark\\spark-sql\\input\\BeijingPM20100101_20151231.csv")

    //写
    df.write.json("output\\beijing_pm5.json")

    //读
    spark.read.json("output\\beijing_pm5.json")
      .show()
  }
}

如果不重新分区, 则会为 DataFrame 底层的 RDD 的每个分区生成一个文件, 为了保持只有一个输出文件, 所以重新分区

保存为 JSON 格式的文件有一个细节需要注意, 这个 JSON 格式的文件中, 每一行是一个独立的 JSON, 但是整个文件并不只是一个 JSON 字符串, 所以这种文件格式很多时候被成为 JSON Line 文件, 有时候后缀名也会变为 jsonl

beijing_pm.jsonl

{"day":"1","hour":"0","season":"1","year":2013,"month":3}
{"day":"1","hour":"1","season":"1","year":2013,"month":3}
{"day":"1","hour":"2","season":"1","year":2013,"month":3}

JSON 格式的文件是有结构信息的, 也就是 JSON 中的字段是有类型的, 例如 “name”: “zhangsan” 这样由双引号包裹的 Value, 就是字符串类型, 而 “age”: 10 这种没有双引号包裹的就是数字类型, 当然, 也可以是布尔型 “has_wife”: true

Spark 读取 JSON Line 文件的时候, 会自动的推断类型信息

val spark: SparkSession = ...

val dfFromJSON = spark.read.json("dataset/beijing_pm_json")

dfFromJSON.printSchema()

在这里插入图片描述
Spark 可以从一个保存了 JSON 格式字符串的 Dataset[String] 中读取 JSON 信息, 转为 DataFrame

这种情况其实还是比较常见的, 例如如下的流程

在这里插入图片描述
假设业务系统通过 Kafka 将数据流转进入大数据平台, 这个时候可能需要使用 RDD 或者 Dataset 来读取其中的内容, 这个时候一条数据就是一个 JSON 格式的字符串, 如何将其转为 DataFrame 或者 Dataset[Object] 这样具有 Schema 的数据集呢? 使用如下代码就可以

val spark: SparkSession = ...

import spark.implicits._

val peopleDataset = spark.createDataset(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)

spark.read.json(peopleDataset).show()





 /**
   * 从消息队列中取出JSON格式的数据,需要使用SparkSQL 进行处理
   */

  @Test
  def json2(): Unit = {
    val df = spark.read
      .option("header", value = true)
      .csv("E:\\Project\\Spark\\spark-sql\\input\\BeijingPM20100101_20151231.csv")

    val jsonRDD = df.toJSON.rdd
    spark.read.json(jsonRDD).show()
  


总结

1,JSON 通常用于系统间的交互, Spark 经常要读取 JSON 格式文件, 处理, 放在另外一处
2,使用 DataFrameReader 和 DataFrameWriter 可以轻易的读取和写入 JSON, 并且会自动处理数据类型信息

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值