v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}

Normal
0
false

false
false
false

EN-US
ZH-CN
X-NONE

/* Style Definitions */
table.MsoNormalTable
{mso-style-name:"Table Normal";
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0in 5.4pt 0in 5.4pt;
mso-para-margin-top:0in;
mso-para-margin-right:0in;
mso-para-margin-bottom:8.0pt;
mso-para-margin-left:0in;
line-height:107%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Calibri",sans-serif;
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;}

1. DataFramesDatasets,与SparkSQL

Spark SQL 以及它的 DataFrames和Datasets 接口是Spark性能的未来,它们提供了更高效的存储选择,高级的优化器,以及在序列化数据上的直接操作。

这些组件对于获取Spark高性能至关重要。下图是一个性能对比:

与RDDs一样,DataFrames与Datasets代表的是分布式数据集合,但是它们相对于RDDs来说,还会保有额外的schema信息。这个额外的schema信息可用于提供更高效的存储层(Tungsten),并在优化器中(Catalyst)可以执行额外的优化。

除了schema信息,对于在Datasets与DataFrames上执行的操作,优化器中可以检查它们的逻辑含义,而不仅是执行函数而已。

2. SparkSessionor HiveContext or SQLContext

在Spark应用中,SparkContext为它的entry point;在流程序中,对应的为 StreamingContext。而在SparkSQL中,它的entry point为SparkSession。正如其他的Spark组件一样,我们需要import以下额外的组件,以使用SparkSQL:

import org.apache.spark.sql.{DataFrame, Dataset, SparkSession, Row}
import org.apache.spark.sql.catalyst.expressions.aggregate._
import org.apache.spark.sql.expressions._
import org.apache.spark.sql.functions._

如果使用的是Spark Shell,则会自动获取一个SparkSession,名为spark(如Spark中对应的sc)。

SparkSession一般是使用builder 模式创建,使用的方法是getOrCreate()。如果已存在一个session,则直接获取,否则创建一个新的。此builder可以接受基于string的key-value 配置项:config(key, value);以及一些常见配置的快捷方法。

在快捷配置中,其中比较重要的一个是enableHiveSupport(),它可以在不需要安装 Hive 的情况下,给用户提供访问Hive UDFs的权限(但是需要引入额外的jar包)。下面的例子展示了如何创建一个支持 Hive 的SparkSession:


val session = SparkSession.builder()
.enableHiveSupport()
.getOrCreate() // Import the implicits, unlike in core Spark the implicits are defined
// on the context.
import session.implicits._

enableHiveSupport() 这个快捷方法不仅配置了Spark SQL 使用这些 Hive jars,同时也会主动检查hive 类是否能被导入。如果不能导入,(相对于手动指定config值)则会抛出更清晰的报错信息,具体实现源码为:

/**
* Enables Hive support, including connectivity to a persistent Hive metastore, support for
* Hive serdes, and Hive user-defined functions.
*
* @since 2.0.0
*/
def enableHiveSupport(): Builder = synchronized {
if (hiveClassesArePresent) {
config(CATALOG_IMPLEMENTATION.key, "hive")
} else {
throw new IllegalArgumentException(
"Unable to instantiate SparkSession with Hive support because " +
"Hive classes are not found.")
}
} /**
* @return true if Hive classes can be loaded, otherwise false.
*/
private[spark] def hiveClassesArePresent: Boolean = {
try {
Utils.classForName(HIVE_SESSION_STATE_BUILDER_CLASS_NAME)
Utils.classForName("org.apache.hadoop.hive.conf.HiveConf")
true
} catch {
case _: ClassNotFoundException | _: NoClassDefFoundError => false
}
}

可以看到,调用enableHiveSupport()方法后,会自动检查Hive 相关 classes是否能导入,若无法导入则会报错。

若是API中有提供快捷配置方法,则优先建议使用这些方法做配置。因为常规的config接口中,不会做配置验证。

对于getOrCreate方法,需要注意的是:如果一个session已经在存在了,则你的配置值可能会被忽略,并直接得到一个已存在的SparkSession。一些选项,例如master的配置,也仅会在没有已存在的SparkContext运行时,创建一个新的 SparkContext并应用此配置;否则,直接返回一个已有的SparkContext。

在Spark 2.0以前,在使用Spark SQL时,使用的是两个独立的entry points(HiveContext 或 SQLContext)。它们的主要区别在于:HiveContext不需要安装一个Hive。HiveContext优于SQLContext。如果你有hive依赖冲突,且无法解决时,才建议使用SQLContext。相对于SQLContext,HiveContext有更完整的SQL parser,以及additional UDFs。下面是创建一个HiveContext的例子:


val sc = new SparkContext()
val hiveContext = new HiveContext(sc)
// Import the implicits, unlike in core Spark the implicits are defined
// on the context.
import hiveContext.implicits._
 

在使用时,我们应优先考虑使用SparkSession,然后再考虑HiveContext,最后是SQLContext。有些情况下,可能Spark的库并未更新HiveContext或SQLContext中的方法到SparkSession中,所以可能仍有使用HiveContext/SQLContext的需求。下面是创建SQLContext或HiveContext所需要的额外的导入库信息:

import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.sql.hive.thriftserver._

在使用SparkSQL与Spark-Hive时,需要引入的sbt/maven 库为(以2.4.0 版本为例):

// https://mvnrepository.com/artifact/org.apache.spark/spark-hive
libraryDependencies += "org.apache.spark" %% "spark-hive" % "2.4.0" % "provided" // https://mvnrepository.com/artifact/org.apache.spark/spark-sql
libraryDependencies += "org.apache.spark" %% "spark-sql" % "2.4.0"

2. Schema基础

Schema的信息,以及它启用的优化过功能,是SparkSQL与core Spark之间的一个核心区别。检查schema对于DataFrames尤为重要,因为RDDs与Datasets中没有模板化的类型。无论是加载数据时的引用、还是基于父DataFrames做计算、亦或是在DataFrames上应用transformation,schema一般都是由Spark SQL自动处理。

DataFrames以人类可读、以及程序化格式,这两种方式描述schema。printSchema() 会显示一个DataFrame的schema,此方法常用于spark-shell中,用于及时了解当前处理的数据的schema。这对于数据各执(如JSON)来说特别有用,因为一般若仅查看一小部分数据、或是仅读一个header,可能无法立即判断出数据的schema信息。在程序用法上,我们可以调用schema直接获取数据schema信息(常用于ML pipeline transformers)。鉴于大家可能都已经很熟悉了case classes 和 JSON,这里我们拿JSON举例,看看如何在spark中表示:

JSON 数据:

{

"name":
"mission",

"pandas": [{

"id": 1,

"zip":
"94110",

"pt":
"giant",

"happy":
true,

"attributes":
[0.4, 0.5]

}]

}

对应的 case class:


case class RawPanda(id: Long, zip: String, pt: String, happy: Boolean, attributes: Array[Double])
case class PandaPlace(name: String, pandas: Array[RawPanda])
 

根据创建的 case class,我们可以创建一个本地实例,将它转为一个Dataset,并打印出它的schema信息:


def createAndPrintSchema()={
val damao = RawPanda(1, "M1B 5K7", "giant", true, Array(0.1, 0.1))
val pandaPlace = PandaPlace("toronto", Array(damao))
val df = session.createDataFrame(Seq(pandaPlace))
df
}

使用df.printSchemat打印出的schema信息为:

root

|-- name: string (nullable = true)

|-- pandas: array (nullable = true)

|    |-- element: struct (containsNull = true)

|    |    |-- id: long (nullable = false)

|    |    |-- zip: string (nullable = true)

|    |    |-- pt: string (nullable = true)

|    |    |-- happy: boolean (nullable = false)

|    |    |-- attributes: array (nullable = true)

|    |    |    |-- element: double (containsNull = false)

Schema信息除了便于人类阅读外,也可以用于编程时使用。用于编程的schema 可以通过df.schema 返回,例如:

> df.schema

res0: org.apache.spark.sql.types.StructType = StructType(

StructField( name,StringType,true),

StructField( pandas,

ArrayType(

StructType( StructField( id,LongType,false),

StructField( zip,StringType,true),

StructField( pt,StringType,true),

StructField( happy,BooleanType,false),

StructField(attributes,ArrayType(DoubleType,false),true)),

true),true))

可以看到返回的是一个StructType类型,里面的元素是StructField,它的case class定义如下

case class StructField(
    name: String,
    dataType: DataType,
    nullable: Boolean = true,
    metadata: Metadata = Metadata.empty) {

这里可以注意到,StructField可以嵌套StructField,正如case class嵌套一样。StructFiled的参数指定name、type、以及一个布尔值表示此字段可能是null或是缺失。

2. DataFrameAPI

DataFrameAPI可用于操作DataFrames,过程不需要注册临时表或产生任何SQL表达式。它也包含transformations和actions两类操作。

Transformations

在DataFrameAPI中的transformation与RDD中的概念类似,但是有更多的关系型风格。在RDD中,我们可以使用任意的函数,对此,optimizer无法对此进行优化。而在DataFrameAPI中,通过使用有限制的表达式语法,optimizer 可以有更多的信息用于优化。和RDD一样,我们可以将DataFrame中的变换(transformations)大致分解为简单单个DataFrame、多个DataFrame、key/value、以及grouped/windowed transformations。

这里需要注意的是:SparkSQL 变换仅仅是部分惰性的,因为schema是立即计算的

简单的DataFrame变换与SQL表达式

DataFrame中的变换与RDD中的基本一致,它们的区别是:在DataFrame中使用的是Spark SQL 表达式,而RDD中用的是arbitrary functions。DataFrame中的函数,例如filter,接受的是Spark SQL表达式,而不是lambda函数。下面我们以DataFrames中不同的filter操作为例,进行进一步探讨。

首先创建两个示例数据:


def createPandaInfo()={
val damao = RawPanda(1, "M1B 5K7", "giant", true, Array(0.1, 0.1))
val tuantuan = RawPanda(2, "B11, 7JK", "small", false, Array(0.05, 0.1))
val df = session.createDataFrame(Seq(damao, tuantuan))
df
}
 

> val pandaInfo = createPandaInfo

pandaInfo: org.apache.spark.sql.DataFrame = [id: bigint, zip: string ... 3 more fields]

对于DataFrame,(假设这里的一个实例化变量是df)我们可以通过df(“列名”) 访问到列的信息,注意这里只是列的信息,并不是列的数据,例如:

scala> pandaInfo.columns

res37: Array[String] = Array(id, zip, pt, happy, attributes)

scala> pandaInfo("happy")

res38: org.apache.spark.sql.Column = happy

在执行filter时,若是需要从列上筛选,则在指定列上给出filter语句即可,例如:

pandaInfo.filter(pandaInfo("happy") !== true).collect

res45: Array[org.apache.spark.sql.Row] = Array([2,B11, 7JK,small,false,WrappedArray(0.05, 0.1)])

也可以对多个列执行过滤:

pandaInfo.filter(

| (pandaInfo("happy") !== true).and(pandaInfo("attributes")(0) < pandaInfo("attributes")(1))

| ).collect

res53: Array[org.apache.spark.sql.Row] = Array([2,B11, 7JK,small,false,WrappedArray(0.05, 0.1)])

从上面的例子可以看到,可以对嵌套元素进行访问,例如pandaInfo("attributes")(0),以及多个过滤条件之间使用 .and 方式进行连接。

还有一点需要注意的是,在与常数进行比较时,必须将列名写在常数前面,例如:

> pandaInfo.filter(0 < pandaInfo("id")).collect

<console>:43: error: overloaded method value < with alternatives:

(x: Double)Boolean <and>

cannot be applied to (org.apache.spark.sql.Column)

> pandaInfo.filter( pandaInfo("id") > 0).collect

res60: Array[org.apache.spark.sql.Row] = Array([1,M1B 5K7,giant,true,WrappedArray(0.1, 0.1)], [2,B11, 7JK,small,false,WrappedArray(0.05, 0.1)])

因为SparkSQL的 column operator 是定义在 column class上的,所以表达式 0 >= pandaInfo.col("id") 不会被编译,因为Scala会在 0 上使用 >= 的操作符,而不是在列上使用。所以此时需要将列写在前面,或是将0转换为一个column(使用lit方法),例如:
scala> lit(0)

res63: org.apache.spark.sql.Column = 0

scala> pandaInfo.filter( lit(0) <
pandaInfo.col("id")).collect

res64: Array[org.apache.spark.sql.Row] = Array([1,M1B
5K7,giant,true,WrappedArray(0.1, 0.1)], [2,B11,
7JK,small,false,WrappedArray(0.05, 0.1)])

filter仅是一个很简单的例子,对于更多DataFrame标准方法,可以去参考SparkSQL官方API文档。

3. DataFramesDatasets中的数据表示

DataFrames 相对于RDDs,更是一种行式对象;且DataFrames与Datasets有专门的表示方法、以及列式缓存格式。这种专门的表示方法,不仅可以更高效的使用存储空间,甚至还可以实现比Kryo序列化更快的编码。不过有一点需要说明的是,DataFrames与Datasets与RDDs一样,一般都是惰性执行的,并建立在它们的依赖谱系之上(除了DataFrames中,它被称为一个logical
plan,且包含更多的信息)。

Tungsten

Tungsten是一个新的Spark SQL组件,通过直接在byte
level运作,提供更高效的Spark操作。下面我们对比一下
DataFrames与RDDs的cache情况:

首先是 DataFrames:


def dfcahce()={
val rawdata = sc.textFile("s3://xxxx/ ")
val caseRdd = rawdata.map(x => {
val words = x.split(",")
item(words(0), words(1), words(2), words(3), words(4))
}) val dfcache = session.createDataFrame(caseRdd)
dfcache.persist()
dfcache.filter(dfcache("id") === 12545).collect
}
 

执行代码查看cache情况:

然后是RDD:


def rddCache()={
val rawdata = sc.textFile("s3://tang-emr/mediumdata/")
val caseRdd = rawdata.map(x => {
val words = x.split(",")
item(words(0), words(1), words(2), words(3), words(4))
}) caseRdd.persist()
caseRdd.filter(_.id == "12545").collect }

执行代码后查看cache情况:

有关storage,这里需要注意的是,在执行了persist() 方法后,会将RDD数据缓存在executor的内存中。所以此时即使一个job结束后,container并不会被回收。因为它们需要将数据缓存在内存。在一个application结束后,Storage页面就不会再有RDD缓存的信息显示。

使用Tungsten时,它所占用的对象空间远小于序列化对象(使用Java,甚至是 Kryo序列化)空间。而因为Tungsten并不依赖于Java对象,所以堆内、堆外的内存分配均是支持的。Tungsten除了存储格式更紧凑外,相对于原生序列化,Tungsten序列化的时间也会更快。

需要注意的是,因为Tungsten不再依赖于Java 对象而工作,所以我们可以使用on-heap(in the JVM)或是 off-heap 存储。如果使用off-heap存储,则需要给container足够的空间用于off-heap的分配。这个可以通过web UI获取更清晰的信息。

3.DataSets

DataSets 是SparkSQL的一个扩展,提供了额外的编译-时间类型检查(compile-time type checking)。在Spark 2.0之后,DataFrames成为Datasets的一个特殊的版本,用于处理通用Row对象,且不包含Datasets的常规编译-时间类型检查。

从代码上看,DataSets与DataFrames的关系为:

type DataFrame = Dataset[Row]

在不同使用场景中,使用DataSets而不使用DataFrames
的其中一个原因有:DataFrames
有runtime
schema information,但是缺少compile-time information about
schema。

DataSets提供了类似RDD的变换,例如:

def funMap(ds: Dataset[RawPanda]): Dataset[Double] = {
  ds.map{rp => rp.attributes.filter(_ > 0).sum}
}

更多有关DataSets的使用方法不在此详述。

3. Query Optimizer

Catalyst 是Spark SQL 查询优化器,它接收一个query plan,将它转换为一个Spark可以执行的execution plan。正如根据RDDs的transformation 构造一个DAG 一样,我们在DataFrames/Datasets 应用的relational and functional transformations 会被Spark SQL 用于构建一个query plan的树,成为logical plan。Spark可以在logical plan上应用一些优化,并使用cost-based 模型为同一个logical plan对应的多个physical plan中做选择。

Logical and Physical Plans

根据DataFrames/Datasets(或SQL queries)上应用的transformations,会先构造一个未解析的(unresolved)logical plan。Spark optimizer 的运行分为多个阶段,它需要先解析references、以及表达式的类型,然后再执行优化。

解析后的plan称为logical plan,Spark在logical plan上直接应用一系列的简化操作,产生一个优化的(optimized)logical plan。

在logical plan被优化后,Spark会产生一个physical plan。Physical plan阶段会使用rule-based和cost-based 优化,产生一个最优的physical plan。这个阶段中最重要的优化之一是谓词下推(predicate pushdown)到数据源层。

代码生成

作为最后一步,Spark可能为组件应用代码生成(apply code generation)。代码生成由Janino编译Java代码完成。早期版本使用Scala的Quasi Quotes,但是此方法为小数据集启用code generation时会产生很高的负载。在一些TPCDS queries中,code generation 可以提升10倍以上的性能。

Large Query Plans 与迭代算法

Catalyst 已经相当强大了,但在一些场景中仍会有些挑战,其中之一就是超大型的query plans。这些query plans一般是迭代算法产生,例如图算法或是机器学习算法。对此,一个简单的解决方案是将数据转回RDD,然后在每轮迭代后转回DataFrame/DataSet,如:

val rdd = df.rdd
rdd.cache()
sqlCtx.createDataFrame(rdd, df.schema)

假设即使你用的是python,也尽量确保使用底层的Java RDD以提升性能。

使用DataFrame 迭代算法会导致query plan 生成较慢的问题是一个已知issue,apache jira链接如下:

https://issues.apache.org/jira/browse/SPARK-13346

References:

[1] Holden Karau and Rachel Warren, High Performance Spark, 2019

v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}

Normal
0
false

false
false
false

EN-US
ZH-CN
X-NONE

/* Style Definitions */
table.MsoNormalTable
{mso-style-name:"Table Normal";
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0in 5.4pt 0in 5.4pt;
mso-para-margin-top:0in;
mso-para-margin-right:0in;
mso-para-margin-bottom:8.0pt;
mso-para-margin-left:0in;
line-height:107%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Calibri",sans-serif;
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;}

DataFrames,Datasets,与 SparkSQL的更多相关文章

  1. Introducing Apache Spark Datasets(中英双语)

    文章标题 Introducing Apache Spark Datasets 作者介绍 Michael Armbrust, Wenchen Fan, Reynold Xin and Matei Zah ...

  2. 学习Spark2.0中的Structured Streaming(一)

    转载自:http://lxw1234.com/archives/2016/10/772.htm Spark2.0新增了Structured Streaming,它是基于SparkSQL构建的可扩展和容 ...

  3. Apache Spark 2.2.0 中文文档

    Apache Spark 2.2.0 中文文档 - 快速入门 | ApacheCN Geekhoo 关注 2017.09.20 13:55* 字数 2062 阅读 13评论 0喜欢 1 快速入门 使用 ...

  4. 入门大数据---Spark整体复习

    一. Spark简介 1.1 前言 Apache Spark是一个基于内存的计算框架,它是Scala语言开发的,而且提供了一站式解决方案,提供了包括内存计算(Spark Core),流式计算(Spar ...

  5. Spark结构式流编程指南

    Spark结构式流编程指南 概览 Structured Streaming 是一个可拓展,容错的,基于Spark SQL执行引擎的流处理引擎.使用小量的静态数据模拟流处理.伴随流数据的到来,Spark ...

  6. Structured Streaming Programming Guide结构化流编程指南

    目录 Overview Quick Example Programming Model Basic Concepts Handling Event-time and Late Data Fault T ...

  7. Structured streaming

    Structured streaming是spark 2.0以后新增的用于实时处理的技术.与spark streaming不同的是,Structured streaming打开了数据源到数据落地之间的 ...

  8. Spark Structured Stream 2

    ❤Limitations of DStream API Batch Time Constraint application级别的设置. 不支持EventTime event time 比process ...

  9. Apache Spark 2.2.0 中文文档 - Structured Streaming 编程指南 | ApacheCN

    Structured Streaming 编程指南 概述 快速示例 Programming Model (编程模型) 基本概念 处理 Event-time 和延迟数据 容错语义 API 使用 Data ...

随机推荐

  1. PTA 1001 A+B Format

    问题描述: Calculate a+b and output the sum in standard format -- that is, the digits must be separated i ...

  2. Vue tinymce富文本编辑器

    tinymce 官方为 vue 项目提供了一个组件 tinymce-vue 一.安装tinymce-vue npm install @tinymce/tinymce-vue -S 二.下载tinymc ...

  3. git pull报错you do not have permission to pull from the repository

    you do not have permission to pull from the repository解决方法   使用git进行项目的版本管理,换了台电脑,配置了账号和邮箱后,pull一个项目 ...

  4. Oracle 12c 多租户家族(12c 18c 19c)如何在 PDB 中添加 HR 模式

    Oracle 12c 多租户家族(12c [12.2.0.1].18c [12.2.0.2].19c [12.2.0.3])如何在 PDB 中添加模式:19c (19.3) 手工添加示例 HR 用户 ...

  5. Csla One or more properties are not registered for this type

    在实际运行中,好好运行的程序出现了以下问题: 2019-12-27 10:40:00,164 [DefaultQuartzScheduler_Worker-2] ERROR IBeam.BCPool. ...

  6. beego——view 模板语法

    一.基本语法 go统一使用{{和}}作为左右标签,没有其它的标签符号. 使用"."来访问当前位置的上下文,使用"$"来引用当前模板根级的上下文,使用$var来访 ...

  7. eclipse中创建了web项目,src下创建子目录是平级的情况

    1.在以下可设置不同的视图 windows->show view菜单 ->点Other......    然后在搜索框里输入你想要的视图 2.在Project Explorer下创建的包看 ...

  8. Codeforce 230A - Dragons (sort)

    Kirito is stuck on a level of the MMORPG he is playing now. To move on in the game, he's got to defe ...

  9. MapReduce第一个项目 WordCount

    参考自林子雨大数据教学:     http://dblab.xmu.edu.cn/blog/hadoop-build-project-using-eclipse/ 创建一个文件夹:放入一个文本文件:填 ...

  10. Learn from Niu 2020.1.28

    1. 泛读和精度的区别和迭代: 泛读: 1个月之内,读50篇论文,进行粗读,了解多维时间序列信号,有哪些research problem, challenges, research groups, r ...