SparkSQL学习笔记(二)DataSource

简介:

SparkSQL通过DataFrame接口支持处理各种数据源,df可以抽象为RDD或注册内存(临时)表处理,临时表可以通过SQL操作并返回一个结果df。


Load/Save 方法:

最简单的格式,默认为parquet(列式存储格式,自身包含表结构和表数据),可以在spark.sql.sources.default里配置。

例:scala

val df = sqlContext.read.load("examples/src/main/resources/users.parquet")
df.select("name", "favorite_color").write.save("namesAndFavColors.parquet")

手动指定类型:

手动选择数据源,数据源的名称可以写全(例:org.apache.spark.sql.parquet),也可以简写(json,parquet,jdbc)。DataFrame通过以下语法可以转换为任何格式。

val df = sqlContext.read.format("json").load("examples/src/main/resources/people.json")
df.select("name", "age").write.format("parquet").save("namesAndAges.parquet")

保存模式:

指定保存模式很重要,因为数据没有使用任何锁,也不是原子性的,通常为了避免被覆盖数据会在写入新数据前先清空。

下面是几种保存模式:

Scala/Java Any Language Meaning
SaveMode.ErrorIfExists(default) "error"(default) 当保存DataFrame到数据文件时, 若数据文件以存在,抛出异常
SaveMode.Append "append" 当保存DataFrame到数据文件时, 若数据文件以存在,新的数据内容会加到数据文件的后面
SaveMode.Overwrite "overwrite" 当保存DataFrame到数据文件时, 若数据文件以存在,新的数据会覆盖原有数据
SaveMode.Ignore "ignore" 当保存DataFrame到数据文件时, 若数据文件以存在,不执行任何操作,类似 CREATE TABLE IF NOT EXISTS in SQL.
保存为持久表:

HiveContext也可以通过saveAsTable把DataFrame保存为持久表,不像registerTempTable只是临时内存表,saveAsTable会给HiveMetaStore内写入数据成为真正的表,当你重启spark项目持久表依旧存在。持久表可以通过SQLcontext的table方法创建为DataFrame。

默认情况下,saveAsTable会创建一个管理表,意味着MetaStore管理着真实的数据地址,当表数据被删除,管理表也将删除对应的数据。


Parquet文件:

parquet是一种列式存储格式,支持许多其他的数据处理系统。SparkSQL提供了read和write方法并自动解析保存数据对应的schema.


Loading Data:

例:scala

import sqlContext.implicits._

val people: RDD[Person] = ... // 上一篇博文内的例子

people.write.parquet("people.parquet")

// 读取上面创建的parquet文件  Parquet文件是"自解释"的,即schema不用再提供
// 读取parquet文件后的结果也是DataFrame
val parquetFile = sqlContext.read.parquet("people.parquet")

parquetFile.registerTempTable("parquetFile")
val teenagers = sqlContext.sql("SELECT name FROM parquetFile WHERE age >= 13 AND age <= 19")
teenagers.map(t => "Name: " + t(0)).collect().foreach(println)

分区表:

表分区是一种常见的系统优化方式例如Hive。分区表中,数据通常存储在不同的目录,分区字段值编码在每个分区目录里。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
            └── ...
通过SQLContext.read.parquet或SQLContext.read.load读取表路径,SparkSQL会自动从路径中提取分区信息。

从DataFrame返回的schema为:

root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)
注意:分区字段的类型是自动识别的,目前支持numeric和string类型。

有时用户不想让它自动识别,可以通过spark.sql.sources.partitionColumnTypeInference.enabled配置,默认为true。关闭自动识别后,string类型会用于分区字段。


合并Schema

像ProtocolBuffer,Avro,Thrift一样,parquet也支持schema evolution。用户可以从一个简单的schema逐步添加更多的schema,通过这种方式,用户希望能合并多个有不同schema的parquet文件。parquet数据源现在支持自动的检测并合并这些文件。

默认情况下从1.5.0开始是关闭的,可以手动的开启:

方法1.当read parquet文件的时候,设置数据源选项mergeSchema为true(下例子).

方法2.设置全局SQL选项,

import sqlContext.implicits._

// 创建一个简单的DF,写入到分区目录
val df1 = sc.makeRDD(1 to 5).map(i => (i, i * 2)).toDF("single", "double")
df1.write.parquet("data/test_table/key=1")

// 创建另一个DF,写入到另一个分区目录,
// 增加一个新的字段并删除已存在的字段
val df2 = sc.makeRDD(6 to 10).map(i => (i, i * 3)).toDF("single", "triple")
df2.write.parquet("data/test_table/key=2")

// 读取分区表
val df3 = sqlContext.read.option("mergeSchema", "true").parquet("data/test_table")
df3.printSchema()

// The final schema consists of all 3 columns in the Parquet files together
// with the partitioning column appeared in the partition directory paths.
// root
// |-- single: int (nullable = true)
// |-- double: int (nullable = true)
// |-- triple: int (nullable = true)
// |-- key : int (nullable = true)

Hive MetaStore parquet table转换:

当对hive metastore parquet table读或写的时候,SparkSQL为了性能会尝试自己支持的的parquet而不是hive提供的。这种行为由spark.sql.hive.convertMetastoreParquet配置控制,默认为开启。


Hive/Parquet Schema 调解:

hive和parquet在处理表结构上有两个关键区别:

1.hive对大小写不敏感,而parquet敏感

2.hive允许字段为空,而parquet对可为空很严格

因此当转换hive metastore parquet table为 Spark SQL parquet table需要协调hive metastore schema和parquet schema .

调解规则为:

1.两个schema中的同名字段不管是否为空,类型必须相同,调解字段的数据类型为parquet的,所以是否为空很重要

2. 只出现在parquet schema中的任何字段都在调解schema中删除。

只出现在hive metastore schema中的任何字段以nullable字段加入到调解schema中。


元数据刷新:

SparkSQL为了更好的性能缓存了parquet metadata。当Hive metastore parquet table的转换为开启的,也会被缓存元数据表。如果这些表被hive或其他的外部工具更新,需要手动的刷新元数据保证元数据一致。

例:scala

// sqlContext is an existing HiveContext
sqlContext.refreshTable("my_table")

配置:

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

Property Name Default Meaning
spark.sql.parquet.binaryAsString false 一些其他Parquet-producing systems, 例如 Impala, Hive, 旧版本的 Spark SQL, 在导出parquet schema时不区分二进制数据和string 这个选项告诉Spark SQL把二进制数据当作string来保证兼容性.
spark.sql.parquet.int96AsTimestamp true 一些 Parquet-producing systems,例如 Impala 和Hive, 存储 Timestamp into INT96. 这个选项告诉Spark SQL把INT96做为timestamp来保证兼容性.
spark.sql.parquet.cacheMetadata true 开启能缓存parquet schema metadata,当查询静态数据时能提升效率.
spark.sql.parquet.compression.codec gzip 设置写parquet文件时的压缩码率,可选的值:uncompressed, snappy, gzip, lzo.
spark.sql.parquet.filterPushdown true 当为true能优化parquet filter push-down
spark.sql.hive.convertMetastoreParquet true 当为 false, Spark SQL 会使用 Hive SerDe 解析 parquet tables 而不是默认spark提供的
spark.sql.parquet.output.committer.class org.apache.parquet.hadoop.
ParquetOutputCommitter

parquet用到的输出类 . 指定的类需要是 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.mergeSchema false

当为 true, parquet数据源从所有数据文件合并schema , 不然schema要从合并文件或随机文件里选择(若没有合并文件).

JSON数据集:

Spark SQL可以自动的识别json数据集的schema并且加载为DataFrame。通过SQLContext.read.json()来转换字符串类型RDD或json文件。

注意这里的json不是传统的json文件内容,每一行要包含分隔,独立有效json对象。因此常规的多行json文件通常会失败。

例:scala

// sc is an existing SparkContext.
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

// 指定json文件路径.
val path = "examples/src/main/resources/people.json"
val people = sqlContext.read.json(path)

// 推测出的schema可以打印出来看
people.printSchema()
// root
//  |-- age: integer (nullable = true)
//  |-- name: string (nullable = true)

// DataFrame注册为临时table.
people.registerTempTable("people")

// sqlContext执行SQL.
val teenagers = sqlContext.sql("SELECT name FROM people WHERE age >= 13 AND age <= 19")

// 二选一, DataFrame也可以通过一个RDD[String]存储了String类型的JSON 对象来创建.
val anotherPeopleRDD = sc.parallelize(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val anotherPeople = sqlContext.read.json(anotherPeopleRDD)

Hive Tables:

Spark SQL也提供了对Hive表的读和写。然而由于hive本身有大量的依赖关系,因此默认它没有被Spark集成,hive的支持需要在Spark的build中加入 -Phive和 -Phive-thriftserver,这个命令会创建一个新的assembly的包含了hive的jar包。需要注意的是,这个jar包必须在每个worker节点都有,因为它们需要访问hive的序列和反序列库(SerDes)来访问hive数据。

把hive的hive-site.xml文件放到conf/下。注意当在yarn集群运行查询时,在lib_managed/jars目录下的datanucleus jars和conf/目录下的hive-site.xml需要是可用的,所有的executors通过YARN集群启动。简单的方法是在spark-submit 后加--jars和--file。

使用Hive必须构造一个HiveContext,它是SQLContext的子集,增加了在MetaStore中发现表和执行HiveQL的功能,即便没有部署hive也依然可以创建hiveContext。当hive-site.xml没有配置时,context会自动在当前目录创建metastore_db和warehouse。

// sc is an existing SparkContext.
val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc)

sqlContext.sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)")
sqlContext.sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")

// Queries are expressed in HiveQL
sqlContext.sql("FROM src SELECT key, value").collect().foreach(println)

和不同版本的Hive MetaStore互动

Spark SQL对hive的支持有一个很重要的目的是与hive metastore互动,允许SparkSQL访问hive表。从Spark1.4.0版本开始,SparkSQL通过以下配置可以被用于查询不同版本的hive metastore。

Property Name Default Meaning
spark.sql.hive.metastore.version 1.2.1 Version of the Hive metastore. 支持 0.12.01.2.1.
spark.sql.hive.metastore.jars builtin Location of the jars that should be used to instantiate the HiveMetastoreClient. This property can be one of three options:
  1. builtin用 Hive 1.2.1,当 -Phive is 开启.与 Spark assembly jar集成, 当选择这种模式,spark.sql.hive.metastore.version 必须为1.2.1或不指定
  2. maven从 Maven repositories下载需要使用到的hive版本. 这个配置一般不建议在生产环境上部署.
  3. A classpath in the standard format for the JVM. This classpath must include all of Hive and its dependencies, including the correct version of Hadoop. These jars only need to be present on the driver, but if you are running in yarn cluster mode then you must ensure they are packaged with you application.
spark.sql.hive.metastore.sharedPrefixes com.mysql.jdbc,
org.postgresql,
com.microsoft.sqlserver,
oracle.jdbc

A comma separated list of class prefixes that should be loaded using the classloader that is shared between Spark SQL and a specific version of Hive. An example of classes that should be shared is JDBC drivers that are needed to talk to the metastore. Other classes that need to be shared are those that interact with classes that are already shared. For example, custom appenders that are used by log4j.

spark.sql.hive.metastore.barrierPrefixes (empty)

A comma separated list of class prefixes that should explicitly be reloaded for each version of Hive that Spark SQL is communicating with. For example, Hive UDFs that are declared in a prefix that typically would be shared (i.e. org.apache.spark.*).

JDBC To Other DataBases:

SparkSQL可以通过JDBC读取外部数据库,此方法应该优先于用JdbcRDD。因为它的返回值为DataFrame,可以很方便的进行操作。Jdbc数据源对java和python也同样很方便因为不需要提供一个ClassTag。

需要在Spark classpath指定使用的jdbc driver,例如,在spark shell中连接postgres用以下命令:

SPARK_CLASSPATH=postgresql-9.3-1102-jdbc41.jar bin/spark-shell

远程数据库里的表会被加载为DataFrame或通过数据源API加载为SparkSQL临时内存表,以下是一些提供的选项:

Property Name Meaning
url The JDBC URL to connect to.
dbtable The JDBC table that should be read. Note that anything that is valid in a FROM clause of a SQL query can be used. For example, instead of a full table you could also use a subquery in parentheses.
driver The class name of the JDBC driver needed to connect to this URL. This class will be loaded on the master and workers before running an JDBC commands to allow the driver to register itself with the JDBC subsystem.
partitionColumn, lowerBound, upperBound, numPartitions These options must all be specified if any of them is specified. They describe how to partition the table when reading in parallel from multiple workers. partitionColumn must be a numeric column from the table in question. Notice that lowerBound and upperBound are just used to decide the partition stride, not for filtering the rows in table. So all rows in the table will be partitioned and returned.

val jdbcDF = sqlContext.read.format("jdbc").options( 
  Map("url" -> "jdbc:postgresql:dbserver",
  "dbtable" -> "schema.tablename")).load()


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王义凯_Rick

遇见即是缘,路过就给个评论吧~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值