Spark(三)-- SparkSQL扩展(数据读写) -- 读写 Parquet、Json 格式文件(二)

7.3 读写 Parquet 格式文件

目标

  1. 理解 Spark 读写 Parquet 文件的语法

  2. 理解 Spark 读写 Parquet 文件的时候对于分区的处理

什么时候会用到 Parquet ?

00a2a56f725d86b5c27463f109c43d8c

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

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

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

使用代码读写 Parquet 文件

默认不指定 format 的时候, 默认就是读写 Parquet 格式的文件

  @Test
  def parquet(): Unit={
    //1.读取csv文件的数据
    val df = spark.read.option("header",value = true).csv("dataset/BeijingPM20100101_20151231.csv")

    //2.把数据写为Parquet格式
    // 写入时:spark默认的文件格式parquet
    // 写入模式:报错,覆盖,追加,忽略
    df.write
      .format("parquet")
      .mode(SaveMode.Overwrite)
      .save("dataset/beijing_pm3")

    //3.读取Parquet格式文件
    // 默认格式是parquet
    // 可以读取文件夹
    spark.read
      .load("dataset/beijing_pm3")
      .show()

  }

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

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

  /**
    * 表分区的概念不仅在parquet上有,其它格式的文件也可以指定表分区
    */
  @Test
  def parquetPartitions():Unit = {
    //1.读取数据
    val df = spark.read
      .option("header",value = true)
      .csv("dataset/BeijingPM20100101_20151231.csv")

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

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

  }

Spark(三)-- SparkSQL扩展(数据读写) -- 读写 Parquet、Json 格式文件(二)_第1张图片

这个地方指的分区是类似 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()

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

dbb274b7fcdfd82c3a3922dfa6bfb29e

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

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

84353e6ed2cf479b82b4d2e4e2b6c3c2

Table 1.  SparkSession 中有关  Parquet 的配置
配置 默认值 含义

spark.sql.parquet.binaryAsString

false

一些其他 Parquet 生产系统, 不区分字符串类型和二进制类型, 该配置告诉 SparkSQL 将二进制数据解释为字符串以提供与这些系统的兼容性

spark.sql.parquet.int96AsTimestamp

true

一些其他 Parquet 生产系统, 将 Timestamp 存为 INT96, 该配置告诉 SparkSQL 将 INT96 解析为 Timestamp

spark.sql.parquet.cacheMetadata

true

打开 Parquet 元数据的缓存, 可以加快查询静态数据

spark.sql.parquet.compression.codec

snappy

压缩方式, 可选 uncompressedsnappygziplzo

spark.sql.parquet.mergeSchema

false

当为 true 时, Parquet 数据源会合并从所有数据文件收集的 Schemas 和数据, 因为这个操作开销比较大, 所以默认关闭

spark.sql.optimizer.metadataOnly

true

如果为 true, 会通过原信息来生成分区列, 如果为 false 则就是通过扫描整个数据集来确定

总结

  1. Spark 不指定 format 的时候默认就是按照 Parquet 的格式解析文件

  2. Spark 在读取 Parquet 文件的时候会自动的发现 Parquet 的分区和分区字段

  3. Spark 在写入 Parquet 文件的时候如果设置了分区字段, 会自动的按照分区存储

7.4 读写 JSON 格式文件

目标

  1. 理解 JSON 的使用场景

  2. 能够使用 Spark 读取处理 JSON 格式文件

什么时候会用到 JSON ?

00a2a56f725d86b5c27463f109c43d8c

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

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

读写 JSON 文件

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

  @Test
  def json():Unit={
    val df = spark.read
      .option("header",value = true)
      .csv("dataset/BeijingPM20100101_20151231.csv")

    df.write.json("dataset/beijing_pm5.json")

    spark.read.json("dataset/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}

也可以通过 DataFrameReader 读取一个 JSON Line 文件

val spark: SparkSession = ...

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

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()

e8a53ef37bbf6675525d1a844f8648f1

Spark 可以从一个保存了 JSON 格式字符串的 Dataset[String] 中读取 JSON 信息, 转为 DataFrame

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

da6f1c7f8d98691117a173e03bfdf18f

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

  /**
    * toJSON的场景:
    * 处理完成后,DataFrame中如果是一个对象,如果其他的系统只支持JSON格式的数据
    * SparkSQL 如果和这种系统进行整合的时候,就需要进行转换
    */
  @Test
  def json1():Unit={
    val df = spark.read
      .option("header",value = true)
      .csv("dataset/BeijingPM20100101_20151231.csv")

    df.toJSON.show()
  }

  /**
    * 从消息队列中取出JSON格式的数据,需要在SparkSQL中处理
    */
  @Test
  def json2():Unit={
    val df = spark.read
      .option("header",value = true)
      .csv("dataset/BeijingPM20100101_20151231.csv")

    val jsonRdd: RDD[String] = df.toJSON.rdd
    spark.read.json(jsonRdd).show()
  }

总结

  1. JSON 通常用于系统间的交互, Spark 经常要读取 JSON 格式文件, 处理, 放在另外一处

  2. 使用 DataFrameReader 和 DataFrameWriter 可以轻易的读取和写入 JSON, 并且会自动处理数据类型信息

 

 

 

你可能感兴趣的:(Spark)