Spark-SparkSQL数据源

SparkSQL的数据源:结构化的文件(json,parquet),或者是Hive的表,或者是外部的数据库(mysql),也或者是已经存在的RDD。

Spark SQL的DataFrame接口支持多种数据源的操作。一个DataFrame可以进行RDDs方式的操作,也可以被注册为临时表。把DataFrame注册为临时表之后,就可以对该DataFrame执行SQL查询。

Spark SQL的默认数据源为Parquet格式。数据源为Parquet文件时,Spark SQL可以方便的执行所有的操作。修改配置项spark.sql.sources.default,可修改默认数据源格式。

一、通用加载/保存方法

1.1 手动指定选项

  • Load是用来读取文件的时候加载文件中的数据
  • Save是用来往外写文件的时候存储写出的数据
val df = spark.read.load("examples/src/main/resources/users.parquet") 

df.select("name", "favorite_color").write.save("namesAndFavColors.parquet")
 
 
  • 1
  • 2
  • 3

当数据源格式不是parquet格式文件时,需要手动指定数据源的格式。数据源格式需要指定全名(例如:org.apache.spark.sql.parquet),如果数据源格式为内置格式,则只需要指定简称定json, parquet, jdbc, orc, libsvm, csv, text来指定数据的格式。
可以通过SparkSession提供的read.load方法用于通用加载数据,使用write和save保存数据。

val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")


peopleDF.write.format("parquet").save("hdfs://master01:9000/namesAndAges.parquet")
 
 
  • 1
  • 2
  • 3

除此之外,可以直接运行SQL在文件上:

val sqlDF = spark.sql("SELECT * FROM parquet.`hdfs://hadoop0:9000/namesAndAges.parquet`")

sqlDF.show()
 
 
  • 1
  • 2
  • 3

1.2 文件保存选项

可以采用SaveMode执行存储操作,SaveMode定义了对数据的处理模式。需要注意的是,这些保存模式不使用任何锁定,不是原子操作。此外,当使用Overwrite方式执行时,在输出新数据之前原数据就已经被删除。SaveMode详细介绍如下表:

Scala/JavaAny LanguageMeaning
SaveMode.ErrorIfExists(default)“error”(default)如果文件存在,则报错
SaveMode.Append“append”追加
SaveMode.Overwrite“overwrite”覆写
SaveMode.Ignore“ignore”数据存在,则忽略

二、Parquet文件

Parquet是一种流行的列式存储格式,可以高效地存储具有嵌套字段的记录。

这里写图片描述

2.1 Parquet读写

Parquet格式经常在Hadoop生态圈中被使用,它也支持Spark SQL的全部数据类型。Spark SQL 提供了直接读取和存储 Parquet 格式文件的方法。

import spark.implicits._

val peopleDF = spark.read.json("examples/src/main/resources/people.json")



peopleDF.write.parquet("hdfs://hadoop0:9000/people.parquet")


val parquetFileDF = spark.read.parquet("hdfs://hadoop0:9000/people.parquet")


parquetFileDF.createOrReplaceTempView("parquetFile")


val namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")


namesDF.map(attributes => "Name: " + attributes(0)).show()


 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.2 解析分区信息

对表进行分区是对数据进行优化的方式之一。在分区的表内,数据通过分区列将数据存储在不同的目录下。Parquet数据源现在能够自动发现并解析分区信息。例如,对人口数据进行分区存储,分区列为gender和country,使用下面的目录结构:

path
└── to
    └── table
        ├── gender=male
        │   ├── ...
        │   │
        │   ├── country=US
        │   │   └── data.parquet
        │   ├── country=CN
        │   │   └── data.parquet
        │   └── ...
        └── gender=female
            ├── ...
            │
            ├── country=US
            │   └── data.parquet
            ├── country=CN
            │   └── data.parquet
            └── ...
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

通过传递path/to/table给 SQLContext.read.parquet或SQLContext.read.load,Spark SQL将自动解析分区信息。返回的DataFrame的Schema如下:

root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

需要注意的是,数据的分区列的数据类型是自动解析的。当前,支持数值类型和字符串类型。自动解析分区类型的参数为:spark.sql.sources.partitionColumnTypeInference.enabled,默认值为true。如果想关闭该功能,直接将该参数设置为disabled。此时,分区列数据格式将被默认设置为string类型,不再进行类型解析。

2.3 Schema合并

像ProtocolBuffer、Avro和Thrift那样,Parquet也支持Schema evolution(Schema演变)。用户可以先定义一个简单的Schema,然后逐渐的向Schema中增加列描述。通过这种方式,用户可以获取多个有不同Schema但相互兼容的Parquet文件。现在Parquet数据源能自动检测这种情况,并合并这些文件的schemas。

因为Schema合并是一个高消耗的操作,在大多数情况下并不需要,所以Spark SQL从1.5.0开始默认关闭了该功能。可以通过下面两种方式开启该功能:

  • 当数据源为Parquet文件时,将数据源选项mergeSchema设置为true
  • 设置全局SQL选项spark.sql.parquet.mergeSchema为true
import spark.implicits._

val df1 = sc.makeRDD(1 to 5).map(i => (i, i * 2)).toDF("single", "double")


df1.write.parquet("hdfs://hadoop0:9000/data/test_table/key=1")


val df2 = sc.makeRDD(6 to 10).map(i => (i, i * 3)).toDF("single", "triple")


df2.write.parquet("hdfs://hadoop0:9000/data/test_table/key=2")


val df3 = spark.read.option("mergeSchema", "true").parquet("hdfs://master01:9000/data/test_table")


df3.printSchema()

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

结果如下:

root
|-- single: int (nullable = true)

|-- double: int (nullable = true) 
|-- triple: int (nullable = true)

|-- key : int (nullable = true)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

2.4 配置

parquet的配置可以通过SQLContext的setConf方法或执行set key=balue命令行:

Property NameDefaultMeaning
spark.sql.parquet.binaryAsStringfalse一些其他Parquet-producing systems, 例如 Impala, Hive, 旧版本的 Spark SQL, 在导出parquet schema时不区分二进制数据和string 这个选项告诉Spark SQL把二进制数据当作string来保证兼容性.
spark.sql.parquet.int96AsTimestamptrue一些 Parquet-producing systems,例如 Impala 和Hive, 存储 Timestamp into INT96. 这个选项告诉Spark SQL把INT96做为timestamp来保证兼容性.
spark.sql.parquet.cacheMetadatatrue开启能缓存parquet schema metadata,当查询静态数据时能提升效率.
spark.sql.parquet.compression.codecgzip设置写parquet文件时的压缩码率,可选的值:uncompressed, snappy, gzip, lzo.
spark.sql.parquet.filterPushdowntrue当为true能优化parquet filter push-down
spark.sql.hive.convertMetastoreParquettrue当为 false, Spark SQL 会使用 Hive SerDe 解析 parquet tables 而不是默认spark提供的
spark.sql.parquet.output.committer.classorg.apache.parquet.hadoop.ParquetOutputCommitterparquet用到的输出类 . 指定的类需要是 org.apache.hadoop.mapreduce.OutputCommitter的子类. 通常,它也是org.apache.parquet.hadoop.ParquetOutputCommitter的子类.Note:这个选项会被忽视如果 spark.speculation 为开启的.这个选项必须通过 Hadoop Configuration 来配置而不是 Spark SQLConf.这个选项重写了spark.sql.sources.outputCommitterClass.Spark SQL 有一个内置的类org.apache.spark.sql.parquet.DirectParquetOutputCommitter, 可以更高效的写入paruquet到S3
spark.sql.parquet.mergeSchemafalse当为 true, parquet数据源从所有数据文件合并schema , 不然schema要从合并文件或随机文件里选择(若没有合并文件).

三、Hive数据库

Apache Hive是Hadoop上的SQL引擎,Spark SQL编译时可以包含Hive支持,也可以不包含。包含Hive支持的Spark SQL可以支持Hive表访问、UDF(用户自定义函数)以及 Hive 查询语言(HiveQL/HQL)等。需要强调的一点是,如果要在Spark SQL中包含Hive的库,并不需要事先安装Hive。一般来说,最好还是在编译Spark SQL时引入Hive支持,这样就可以使用这些特性了。如果你下载的是二进制版本的 Spark,它应该已经在编译时添加了 Hive 支持。

若要把Spark SQL连接到一个部署好的Hive上,你必须把hive-site.xml复制到 Spark的配置文件目录中($SPARK_HOME/conf)。即使没有部署好Hive,Spark SQL也可以运行。 需要注意的是,如果你没有部署好Hive,Spark SQL会在当前的工作目录中创建出自己的Hive 元数据仓库,叫作 metastore_db。此外,如果你尝试使用 HiveQL 中的 CREATE TABLE (并非 CREATE EXTERNAL TABLE)语句来创建表,这些表会被放在你默认的文件系统中的 /user/hive/warehouse 目录中(如果你的 classpath 中有配好的 hdfs-site.xml,默认的文件系统就是 HDFS,否则就是本地文件系统)。

Spark系列(十五)SparkSQL–执行SparkSQL查询
IDEA使用Spark内嵌Hive应用,在原先SparkSQL项目依赖基础上增加Hive依赖

    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-hive_2.11</artifactId>
        <version>${spark.version}</version>
        <!--<scope>provided</scope>-->
    </dependency>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

指定spark.sql.warehouse.dir为本地绝对路径,如果为HDFS路径,请注意看后面内嵌Hive应用注意事项

完整代码如下:

package com.m.jd

import java.io.File

import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, SparkSession}

object HiveOperation extends App {

  val warehouseLocation = new File("/opt/spark-warehouse").getAbsolutePath

  private val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("Spark Hive Example")

  private val session: SparkSession =   SparkSession.builder().appName("Spark Hive Example").config(sparkConf).config("spark.sql.warehouse.dir",warehouseLocation).enableHiveSupport().getOrCreate()

  import session.implicits._
  import org.apache.spark.sql._
  import session.sql

  sql("CREATE TABLE IF NOT EXISTS test (key INT, value STRING) row format delimited fields terminated by '\t'")

  sql("LOAD DATA LOCAL INPATH '/opt/spark-data/kv1.txt' INTO TABLE test")

  private val df: DataFrame = sql("SELECT * FROM test")

  df.show()

+---+------+
|key| value|
+---+------+
| 88|val_88|
| 67|val_67|
+---+------+

  val sqlDF = sql("SELECT key, value FROM test WHERE key > 10 ORDER BY key")

  private val stringsDS: Dataset[String] = sqlDF.map{case Row(key: Int, value: String) => s"Key-value:$key-$value"}

  stringsDS.show()

+-------------------+
|              value|
+-------------------+
|Key-value:67-val_67|
|Key-value:88-val_88|
+-------------------+

  session.close()
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

3.1 内嵌Hive应用

如果要使用内嵌的Hive,什么都不用做,直接用就可以了。

--conf : spark.sql.warehouse.dir=
 
 
  • 1

这里写图片描述

注意:如果你使用的是内部的Hive,在Spark2.0之后,spark.sql.warehouse.dir用于指定数据仓库的地址,如果你需要是用HDFS作为路径,那么需要将core-site.xml和hdfs-site.xml 加入到Spark conf目录,否则只会创建master节点上的warehouse目录,查询时会出现文件找不到的问题,这时需要使用HDFS,则需要将metastore删除,重启集群。

3.2 外部Hive应用

如果想连接外部已经部署好的Hive,需要通过以下几个步骤。

1)将Hive中的hive-site.xml拷贝或者软连接到Spark安装目录下的conf目录下。
2)打开spark shell,注意带上访问Hive元数据库的JDBC客户端。

$ bin/spark-shell --master spark://hadoop0:7077 --jars mysql-connector-java-5.1.27-bin.jar
 
 
  • 1

注意,需要将相关的数据库驱动放到spark的类路径下($SPARK_HOME/jars)。


四、JSON数据集

Spark SQL 能够自动推测 JSON数据集的结构,并将它加载为一个Dataset[Row]. 可以通过SparkSession.read.json()去加载一个 Dataset[String]或者一个JSON 文件.注意,这个JSON文件不是一个传统的JSON文件,每一行都得是一个JSON串。

前面已经介绍了很多SparkSession.read.json()去加载一个JSON文件的例子了,这里介绍去加载一个Dataset[String]的例子:

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


val otherPeople = spark.read.json(otherPeopleDataset)


otherPeople.show()

// +---------------+----+

// |        address|name|

// +---------------+----+

// |[Columbus,Ohio]| Yin|

// +---------------+----+
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

五、JDBC

Spark SQL可以通过JDBC从关系型数据库中读取数据的方式创建DataFrame,通过对DataFrame一系列的计算后,还可以将数据再写回关系型数据库中。

注意,需要将相关的数据库驱动放到spark的类路径下。

$ bin/spark-shell --master spark://hadoop0:7077 --jars mysql-connector-java-5.1.27-bin.jar
 
 
  • 1
// Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods
// Loading data from a JDBC source

val jdbcDF = spark.read.format("jdbc").option("url", "jdbc:mysql://hadoop0:3306/rdd").option("dbtable", " rddtable").option("user", "root").option("password", "123456").load()

val connectionProperties = new Properties()
connectionProperties.put("user", "root")
connectionProperties.put("password", "123456")

val jdbcDF2 = spark.read
.jdbc("jdbc:mysql://hadoop0:3306/rdd", "rddtable", connectionProperties)

// Saving data to a JDBC source

jdbcDF.write
.format("jdbc")
.option("url", "jdbc:mysql://hadoop0:3306/rdd")
.option("dbtable", "rddtable2")
.option("user", "root")
.option("password", "123456")
.save()

jdbcDF2.write
.jdbc("jdbc:mysql://master01:3306/mysql", "db", connectionProperties)

// Specifying create table column data types on write

jdbcDF.write
.option("createTableColumnTypes", "name CHAR(64), comments VARCHAR(1024)")
.jdbc("jdbc:mysql://master01:3306/mysql", "db", connectionProperties)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

参考文章:https://blog.csdn.net/wsdc0521/article/details/50011349

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值