目录
前言
本书涵盖的内容
这本书需要什么
这本书是给谁的
约定
读者反馈
客户支持
下载示例代码
下载本书的彩色图像
勘误
海盗行为
问题
1. Spark SQL入门
什么是Spark SQL?
介绍SparkSession
了解Spark SQL概念
了解弹性分布式数据集(RDD)
了解数据框和数据集
了解Catalyst优化器
了解Catalyst优化
了解Catalyst 转化
Tungsten项目介绍
在流应用程序中使用Spark SQL
了解结构化流内部
摘要
2.使用Spark SQL处理结构化和半结构化数据
了解Spark应用程序中的数据源
选择Spark数据源
在关系数据库中使用Spark
将Spark与MongoDB(NoSQL数据库)结合使用
结合使用Spark和JSON数据
将Spark与Avro文件结合使用
将Spark与Parquet文件一起使用
在Spark中定义和使用自定义数据源
摘要
3.使用Spark SQL进行数据探索
引入探索性数据分析(EDA)
使用Spark SQL进行基本数据分析
识别丢失的数据
计算基本统计
识别数据异常值
使用Apache Zeppelin可视化数据
使用Spark SQL API采样数据
使用DataFrame / Dataset API进行采样
使用RDD API进行采样
使用Spark SQL创建数据透视表
摘要
4.使用Spark SQL进行数据整理
介绍数据处理
探索数据处理技术
家庭用电量数据预处理
计算基本统计数据和汇总
扩充数据集
执行其他杂项处理步骤
预处理天气数据集
分析缺失数据
使用JOIN操作合并数据
修改文字数据
处理多个输入数据文件
删除停用词
减少时间序列数据
预处理时间序列数据集
处理日期字段
持久化和加载数据
定义日期时间索引
使用TimeSeriesRDD对象
处理丢失的时间序列数据
计算基本统计
处理长度可变的记录
将可变长度记录转换为固定长度记录
从“混乱”列中提取数据
为机器学习准备数据
预处理数据以进行机器学习
创建并运行机器学习管道
摘要
5.在流应用程序中使用Spark SQL
引入流数据应用
构建Spark流应用程序
实现基于滑动窗口的功能
将流数据集与静态数据集结合
在结构化流媒体中使用数据集API
使用输出接收器
使用Foreach Sink在输出上进行任意计算
使用内存槽将输出保存到表
使用文件接收器将输出保存到分区表
监控流查询
将Kafka与Spark结构化流一起使用
介绍Kafka概念
介绍ZooKeeper概念
介绍Kafka-Spark集成
介绍Kafka-Spark结构化流
为自定义数据源编写接收器
摘要
6.在机器学习应用程序中使用Spark SQL
机器学习应用简介
了解Spark ML管道及其组件
了解管道应用程序开发过程中的步骤
特色工程介绍
从原始数据创建新功能
估计功能的重要性
了解降维
产生良好的功能
实施Spark ML分类模型
探索糖尿病数据集
预处理数据
构建Spark ML管道
使用StringIndexer索引分类特征和标签
使用VectorAssembler将特征组装到一栏中
使用Spark ML分类器
创建一个Spark ML管道
创建训练和测试数据集
使用PipelineModel进行预测
选择最佳型号
在管道中更改ML算法
介绍Spark ML工具和实用程序
使用主成分分析选择特征
使用编码器
使用Bucketizer
使用VectorSlicer
使用卡方选择器
使用规范化器
检索我们的原始标签
实施Spark ML集群模型
摘要
7.在图应用程序中使用Spark SQL
大规模图形应用介绍
使用GraphFrames探索图
构造一个GraphFrame
基本图查询和操作
使用GraphFrames进行主题分析
处理子图
应用图算法
保存和加载GraphFrames
分析建模为图形的JSON输入
处理包含多种类型关系的图
了解GraphFrame内部
查看GraphFrame物理执行计划
了解GraphFrames中的分区
摘要
8.在SparkR中使用Spark SQL
介绍SparkR
了解SparkR架构
了解SparkR数据帧
使用SparkR进行EDA和数据处理任务
读写Spark DataFrames
探索Spark DataFrames的结构和内容
在Spark DataFrames上运行基本操作
在Spark DataFrames上执行SQL语句
合并SparkR DataFrame
使用用户定义的函数(UDF)
使用SparkR计算摘要统计信息
使用SparkR进行数据可视化
在地图上可视化数据
可视化图形节点和边
使用SparkR进行机器学习
9.使用Spark SQL开发应用程序
介绍Spark SQL应用程序
了解文本分析应用程序
使用Spark SQL进行文本分析
预处理文本数据
计算可读性
使用单词表
创建数据预处理管道
了解文档语料库中的主题
使用朴素贝叶斯分类器
开发机器学习应用程序
摘要
10.在深度学习应用程序中使用Spark SQL
引入神经网络
了解深度学习
了解表征学习
了解随机梯度下降
在Spark中引入深度学习
介绍CaffeOnSpark
DL4J简介
引入TensorFrames
使用BigDL
调整深度学习模型的超参数
引入深度学习管道
了解监督学习
了解卷积神经网络
使用神经网络进行文本分类
使用深度神经网络进行语言处理
了解递归神经网络
引入自动编码器
摘要
11.调整Spark SQL组件的性能
在Spark SQL中引入性能调整
了解DataFrame / Dataset API
优化数据序列化
了解Catalyst优化
了解数据集/ DataFrame API
了解催化剂转化
可视化Spark应用程序执行
探索Spark应用程序执行指标
使用外部工具进行性能调整
Apache Spark 2.2中基于成本的优化器
了解CBO统计信息收集
统计收集功能
过滤运算符
加入运营商
建造方选择
了解多向JOIN排序优化
使用全阶段代码生成了解性能改进
摘要
12.大型应用程序体系结构中的Spark SQL
了解基于Spark的应用程序架构
使用Apache Spark进行批处理
使用Apache Spark进行流处理
了解Lambda架构
了解Kappa架构
构建可伸缩流处理应用程序的设计注意事项
使用Spark SQL构建健壮的ETL管道
选择适当的数据格式
在ETL管道中转换数据
解决ETL管道中的错误
实施可扩展的监控解决方案
部署Spark机器学习管道
了解典型ML部署环境中的挑战
了解模型评分架构的类型
使用集群管理器
摘要
前言
我们将以Spark SQL的基础知识及其在Spark应用程序中的作用作为本书的开始。初步熟悉Spark SQL之后,我们将集中精力使用Spark SQL执行所有大数据项目共有的任务,例如使用各种类型的数据源,探索性数据分析和数据处理。我们还将看到如何利用Spark SQL和SparkR大规模完成典型的数据科学任务。
有了DataFrame / Dataset API和Catalyst优化器作为Spark SQL的核心,毫不奇怪,它在所有基于Spark技术堆栈的应用程序中都扮演着关键角色。这些应用程序包括大规模的机器学习管道,大规模的图形应用程序和新兴的基于Spark的深度学习应用程序。此外,我们将介绍基于Spark SQL的结构化流应用程序,这些应用程序在连续的应用程序中部署在复杂的生产环境中。
我们还将审查Spark SQL应用程序中的性能调整,包括Spark 2.2中引入的基于成本的优化(CBO)。最后,我们将介绍在实际应用程序中利用Spark模块和Spark SQL的应用程序体系结构。更具体地说,我们将介绍大型Spark应用程序中的关键体系结构组件和模式,架构师和设计人员会发现这些关键的体系结构组件和模式可用作其特定用例的构建基块。
本书涵盖的内容
第1章,Spark SQL入门,概述了Spark SQL,同时通过动手实践使您熟悉Spark环境。
第2章,使用Spark SQL处理结构化和半结构化数据,将帮助您使用Spark处理关系数据库(MySQL),NoSQL数据库(MongoDB),半结构化数据(JSON)和Hadoop生态系统中常用的数据存储格式(Avro和Parquet)。
第3章,使用Spark SQL进行数据探索,演示了如何使用Spark SQL探索数据集,执行基本数据质量检查,生成样本和数据透视表以及使用Apache Zeppelin可视化数据。
第4章,使用Spark SQL进行数据整理,使用Spark SQL执行一些基本的数据整理/整理任务。它还向您介绍了一些处理丢失数据,不良数据,重复记录等的技术。
第5章“在流应用程序中使用Spark SQL”提供了一些使用Spark SQL DataFrame / Dataset API构建流应用程序的示例。此外,它还显示了如何在结构化流应用程序中使用Kafka。
第6章,在机器学习应用程序中使用Spark SQL,重点介绍在机器学习应用程序中使用Spark SQL。在本章中,我们将主要探讨特征工程中的关键概念并实现机器学习管道。
第7章,在Graph Applications中使用Spark SQL,向您介绍GraphFrame应用程序。它提供了使用Spark SQL DataFrame / Dataset API来构建图形应用程序并将各种图形算法应用于图形应用程序的示例。
第8章,将Spark SQL与SparkR结合使用,介绍了SparkR体系结构和SparkR DataFrames API。它提供了将SparkR用于探索性数据分析(EDA)和数据处理任务,数据可视化以及机器学习的代码示例。
第9章,使用Spark SQL开发应用程序,可帮助您使用多个Spark模块来构建Spark应用程序。它提供了将Spark SQL与Spark Streaming,Spark Machine Learning等结合的应用程序示例。
第10章,在深度学习应用程序中使用Spark SQL,向您介绍了Spark中的深度学习。在您深入研究BigDL和Spark之前,它涵盖了一些流行的深度学习模型的基本概念。
第11章“调整Spark SQL组件的性能”向您介绍与调整Spark应用程序有关的基本概念,包括使用编码器进行数据序列化。它还涵盖了Spark 2.2中引入的基于成本的优化器的关键方面,以自动优化Spark SQL执行。
第12章,大型应用程序体系结构中的Spark SQL,教您确定可以在大型应用程序体系结构中使用Spark SQL来实现典型的功能和非功能性需求的用例。
这本书需要什么
本书基于Spark 2.2.0(为Apache Hadoop 2.7或更高版本预构建)和Scala 2.11.8。 对于一个或两个小节,由于某些库的不可用和已报告的错误(与Apache Spark 2.2一起使用),还使用了Spark 2.1.0。 硬件和OS规范包括至少8 GB RAM(强烈建议16 GB),100 GB HDD和OS X 10.11.6或更高版本(或建议用于Spark开发的适当Linux版本)。
这本书是给谁的
如果您是开发人员,工程师或架构师,并且想学习如何在Web规模的项目中使用Apache Spark,那么这本书就是您的理想选择。 假定您具有SQL查询的先验知识。 开始使用本书仅需具备Scala,Java,R或Python的基本编程知识。
Spark SQL入门
Spark SQL是使用Spark开发的所有应用程序的核心。在本书中,我们将详细探讨Spark SQL,包括其在各种类型的应用程序中的用法及其内部工作原理。随着本书的进展,开发人员和架构师将欣赏每章中介绍的技术概念和实践会议。
在本章中,我们将向您介绍与Spark SQL相关的关键概念。我们将从SparkSession开始,SparkSession是Spark 2.0中Spark SQL的新入口点。然后,我们将探索Spark SQL的接口RDD,DataFrame和Dataset API。稍后,我们将解释有关Catalyst优化器和Project Tungsten的开发人员级别的详细信息。
最后,我们将在Spark 2.0中为流应用程序引入一个令人兴奋的新功能,称为结构化流。本章介绍了具体的动手练习(使用公开可用的数据集),因此您在阅读各个部分时可以积极地跟进。
更具体地说,本章中的各节将涵盖以下主题以及实践课程:
什么是Spark SQL?
介绍SparkSession
了解Spark SQL概念
了解RDD,DataFrame和数据集
了解Catalyst优化器
了解钨项目
在连续应用程序中使用Spark SQL
了解结构化流内部
什么是Spark SQL?
Spark SQL是Apache Spark的最高级组件之一。 自从Spark 1.0以来,它一直是核心发行版的一部分,并且支持Python,Scala,Java和R编程API。 如下图所示,Spark SQL组件为Spark机器学习应用程序,流应用程序,图形应用程序和许多其他类型的应用程序体系结构提供了基础。
此类应用程序通常使用Spark ML管道,结构化流和GraphFrames,它们均基于Spark SQL接口(DataFrame / Dataset API)。 这些应用程序以及诸如SQL,DataFrames和Datasets API之类的结构自动获得Catalyst优化器的好处。 该优化器还负责根据较低级别的RDD接口生成可执行查询计划。
我们将在第6章“在机器学习应用程序中使用Spark SQL”中更详细地探讨ML管道。 第7章“在Graph应用程序中使用Spark SQL”将介绍GraphFrames。 在本章中,我们将介绍有关结构化流和Catalyst优化器的关键概念,而在第5章,在流应用程序中使用Spark SQL,以及在第11章,调整Spark SQL组件以提高性能中,我们将获得有关它们的更多详细信息。
在Spark 2.0中,DataFrame API已与Dataset API合并,从而统一了Spark库中的数据处理功能。 这也使开发人员可以使用单个高级且类型安全的API。 但是,Spark软件堆栈不会阻止开发人员在其应用程序中直接使用低级RDD接口。 尽管低级RDD API将继续可用,但仍希望(并建议)大多数开发人员使用高级API,即Dataset和DataFrame API。
此外,Spark 2.0通过包括一个新的ANSI SQL解析器来扩展Spark SQL功能,该解析器支持子查询和SQL:2003标准。 更具体地说,子查询支持现在包括相关/不相关的子查询,以及WHERE / HAVING子句中的IN / NOT IN和EXISTS / NOT EXISTS谓词。
Spark SQL的核心是Catalyst优化器,它利用Scala的高级功能(例如模式匹配)来提供可扩展的查询优化器。 DataFrame,Dataset和SQL查询共享相同的执行和优化管道; 因此,使用这些结构中的任何一个或另一个(或使用任何受支持的编程API)都不会对性能产生影响。 开发人员编写的基于DataFrame的高级代码将转换为Catalyst表达式,然后在通过此管道时转换为低级Java字节码。
SparkSession是与Spark SQL相关的功能的切入点,我们将在下一部分中对其进行详细描述。
介绍SparkSession
在Spark 2.0中,SparkSession表示用于操纵Spark中数据的统一入口点。 它最小化了开发人员在使用Spark时必须使用的不同上下文的数量。 SparkSession替换多个上下文对象,例如SparkContext,SQLContext和HiveContext。 这些上下文现在封装在SparkSession对象中。
在Spark程序中,我们使用构建器设计模式来实例化SparkSession对象。 但是,在REPL环境中(即,在Spark shell会话中),将自动创建SparkSession,并通过一个名为Spark的实例对象将其提供给您。
此时,在计算机上启动Spark Shell,以交互方式执行本节中的代码段。 当外壳启动时,您会注意到屏幕上出现了一堆消息,如下图所示。 您应该看到消息,显示SparkSession对象(作为Spark)的可用性,Spark版本为2.2.0,Scala版本为2.11.8和Java版本为1.8.x。
SparkSession对象可用于配置Spark的运行时配置属性。 例如,Spark和Yarn管理的两个主要资源是CPU和内存。 如果要设置Spark执行器的内核数和堆大小,则可以分别设置spark.executor.cores和spark.executor.memory属性来实现。 在此示例中,我们将这些运行时属性分别设置为2个核心和4 GB,如下所示:
spark.conf.set("spark.executor.cores", "2")
spark.conf.set("spark.executor.memory", "4g")
SparkSession对象可用于从各种来源读取数据,例如CSV,JSON,JDBC,流等。 此外,它还可以用于执行SQL语句,注册用户定义的函数(UDF)以及使用数据集和数据帧。 以下会话说明了Spark中的一些基本操作。
数据的下载地址为:
https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/
首先,我们为文件中的记录定义一个架构。 字段描述可从数据集的下载站点获得。
可以使用show()方法显示新创建的DataFrame
可以使用createOrReplaceTempView()方法将DataFrame注册为SQL临时视图。 这允许应用程序使用SparkSession对象的sql函数运行SQL查询,并将结果作为DataFrame返回。
接下来,我们为DataFrame创建一个临时视图,并对它执行一个简单的SQL语句:
结果DataFrame的内容使用show()方法显示:
在下一个代码片段中,我们向您展示使用case类和toDS()方法创建Spark Dataset的语句。 然后,我们定义一个UDF来将当前包含2和4的clas列分别转换为0和1。 我们使用SparkSession对象注册UDF,并在SQL语句中使用它:
SparkSession公开(通过商品目录属性)访问基础元数据的方法,例如可用的数据库和表,已注册的UDF,临时视图等。 此外,我们还可以缓存表,删除临时视图并清除缓存。 这些语句中的一些及其对应的输出如下所示:
//老版本的sparksql
package com.wangjunji.sparksql
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql._
import org.apache.spark.sql.types.StructType
object UsingDateSet {
def main(args: Array[String]): Unit = {
//配置SparkConf运行参数,在使用集群提交时的方法不一样
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SQL-1")
//启动的内核个数
conf.set("spark.executor.cores", "2")
//启动的内存的大小
conf.set("spark.executor.memory", "1g")
//创建hive warehouse 的位置,这个需要存在,不存在,则报错
conf.set("spark.sql.warehouse.dir",".")
//设置spark core的运行环境
val sc = new SparkContext(conf)
//设置sql运行环境,这种方式已经过期,现应该是使用新的api
val sparksql = new SQLContext(sc)
//设置dataframe的数据类型
import sparksql.implicits._
val recordSchema = new StructType().add("sample", "long").add("cThick", "integer").add("uCSize", "integer").add("uCShape", "integer").add("mAdhes", "integer").add("sECSize", "integer").add("bNuc", "integer").add("bChrom", "integer").add("nNuc", "integer").add("mitosis", "integer").add("clas", "integer")
//将文件读取成dsate
val df: DataFrame = sparksql.read.format("csv").option("header", false).schema(recordSchema).load("F:\\traninMachineexample\\example\\breast-cancer-wisconsin.data")
//数据读取一百行
df.show(100)
//创建临时表
df.createOrReplaceTempView("cancerTable")
val sqlDF = sparksql.sql("select * from cancerTable where sample is not null ")
sqlDF.show(1000)
}
}
新版本的spark sql
package com.wangjunji.sparksql
import org.apache.spark.sql._
import org.apache.spark.sql.types.StructType
import org.apache.spark.{SparkConf, SparkContext}
object UsingDateSetNew {
case class CancerClass(sample: Long, cThick: Int, uCSize: Int, uCShape: Int, mAdhes: Int, sECSize: Int, bNuc: Int, bChrom: Int, nNuc: Int, mitosis: Int, clas: Int)
val fileName = "F:\\traninMachineexample\\example\\breast-cancer-wisconsin.data"
def binarzie(s:Int):Int = s match{case 2=>0
case 4=>1
}
def main(args: Array[String]): Unit = {
//配置SparkConf运行参数,在使用集群提交时的方法不一样
/*val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SQL-1")
//启动的内核个数
conf.set("spark.executor.cores", "2")
//启动的内存的大小
conf.set("spark.executor.memory", "1g")
//创建hive warehouse 的位置,这个需要存在,不存在,则报错
conf.set("spark.sql.warehouse.dir",".")
//设置spark core的运行环境
val sc = new SparkContext(conf)
//设置sql运行环境,这种方式已经过期,现应该是使用新的api*/
val sparksql = SparkSession.builder().
master("local[*]").appName("SQL-2").
config("spark.executor.cores", "2").
config("spark.executor.memory", "1g").
config("spark.sql.warehouse.dir",".").
getOrCreate()
//设置dataframe的数据类型
val recordSchema = new StructType().
add("sample", "long").
add("cThick", "integer").
add("uCSize", "integer").
add("uCShape", "integer").
add("mAdhes", "integer").
add("sECSize", "integer").
add("bNuc", "integer").
add("bChrom", "integer").
add("nNuc", "integer").
add("mitosis", "integer").
add("clas", "integer")
//将文件读取成dsate
val df: DataFrame = sparksql.read.format("csv").
option("header", false).
schema(recordSchema).
load(fileName)
//数据读取一百行
df.show(100)
//创建临时表,并注册成视,从而
df.createOrReplaceTempView("cancerTable")
val sqlDF = sparksql.sql("select * from cancerTable where sample is not null ")
sqlDF.show(1000)
//我们向您展示使用case类和toDS()方法创建Spark Dataset的语句。
//然后,我们定义一个UDF来将当前包含2和4的clas列分别转换为0和1。
// 我们使用SparkSession对象注册UDF,并在SQL语句中使用它:
import sparksql.implicits._
sparksql.sparkContext.textFile(fileName).
map(_.split(",")).
map{item=>CancerClass(item(0).trim.toLong,
item(1).trim.toInt,
item(2).trim.toInt,
item(3).trim.toInt,
item(4).trim.toInt,
item(5).trim.toInt,
item(6).trim.toInt,
item(7).trim.toInt,
item(8).trim.toInt,
item(9).trim.toInt,
item(10).trim.toInt
)}.toDS
sparksql.udf.register("udfValueToCategory",(arg:Int)=>binarzie(arg))
val sqlUDF = sparksql.sql("SELECT *, udfValueToCategory(clas) from cancerTable")
sqlUDF.show(100)
//查看当前的数据库
println(sparksql.catalog.currentDatabase)
//查看某个表是否cache
println(sparksql.catalog.isCached("cancerTable") )
//缓存了某个表
sparksql.catalog.cacheTable("cancerTable")
//查看某个表是否cache了
sparksql.catalog.isCached("cancerTable")
//清理缓存
println(sparksql.catalog.clearCache)
//查看数据库
sparksql.catalog.listDatabases.show()
//取第一个数据库
sparksql.catalog.listDatabases.take(1)
//也可以使用take方法在DataFrame中显示特定数量的记录:
sparksql.catalog.listTables.show()
//我们可以使用以下语句删除之前创建的临时表:
sparksql.catalog.listTables.show()
sparksql.catalog.dropTempView("cancerTable")
//在接下来的几节中,我们将更详细地描述RDD,DataFrame和Dataset构造。
sparksql.catalog.listTables.show()
}
}
SparkSession公开(通过商品目录属性)访问基础元数据的方法,例如可用的数据库和表,已注册的UDF,临时视图等。 此外,我们还可以缓存表,删除临时视图并清除缓存。 这些语句中的一些及其对应的输出如下所示:
了解Spark SQL概念
在本节中,我们将探讨与弹性分布式数据集(RDD),数据帧和数据集,催化剂优化程序和项目钨有关的关键概念。
了解弹性分布式数据集(RDD)
RDD是Spark的主要分布式数据集抽象。 它是不可变,分布,延迟评估,类型推断和可缓存的数据的集合。 在执行之前,将开发人员代码(使用SQL,DataFrames和Dataset API等更高级的结构)转换为RDD的DAG(准备执行)。
您可以通过并行化现有数据集合或访问驻留在外部存储系统(例如文件系统或各种基于Hadoop的数据源)中的数据集来创建RDD。 并行化的集合形成了一个分布式数据集,可以对它们进行并行操作。
您可以通过导入spark.implicits包并使用toDF()方法将RDD隐式转换为DataFrame。
要创建具有特定架构的DataFrame,我们为DataFrame中包含的行定义一个Row对象。 此外,我们拆分了逗号分隔的数据,将其转换为字段列表,然后将其映射到Row对象。 最后,我们使用createDataFrame()创建具有指定架构的DataFrame:
def row(line: List[String] ) : Row = { Row(line(0) .toLong, line(1) .toInt, line(2) .toInt, line(3) .toInt, line(4) .toInt, line(5) .toInt, line(6) .toInt, line(7) .toInt,
line(8) .toInt, line(9) .toInt, line(10) .toInt) }
val data = cancerRDD.map(_.split(",") .to[List] ) .map(row)
val cancerDF = spark.createDataFrame(data, recordSchema)
//此外,我们可以使用前面定义的case类轻松地将前面的DataFrame转换为Dataset:
val cancerDS = cancerDF.as[CancerClass]
RDD数据在逻辑上分为一组分区。此外,所有输入,中间和输出数据也表示为分区。 RDD分区的数量定义了数据分段的级别。这些分区也是并行性的基本单元。 Spark执行作业分为多个阶段,并且由于每个阶段一次在一个分区上运行,因此调整分区的数量非常重要。分区数少于活动阶段数意味着群集可能未得到充分利用,而过多的分区数可能会由于更高的磁盘和网络I / O而影响性能。
RDD的编程接口支持两种类型的操作:转换和操作。转换将从现有的数据集创建一个新的数据集,而操作将返回计算的值或结果。所有转换都被延迟评估-实际执行仅在执行操作以计算结果时才会发生。转换形成谱系图,而不是实际上跨多台计算机复制数据。这种基于图的方法可实现有效的容错模型。例如,如果RDD分区丢失,则可以根据沿袭图重新计算它。
您可以控制数据持久性(例如,缓存)并为RDD分区指定放置首选项,然后使用特定的运算符对其进行操作。默认情况下,Spark将RDD保留在内存中,但是如果没有足够的RAM,它可能会将RDD溢出到磁盘上。缓存将性能提高了几个数量级。但是,它通常占用大量内存。其他持久性选项包括将RDD存储到磁盘并在集群中的各个节点之间复制它们。持久性RDD的内存中存储可以采用反序列化或序列化Java对象的形式。反序列化选项速度更快,而序列化选项的内存效率更高(但更慢)。未使用的RDD会自动从缓存中删除,但具体取决于您的要求;如果不再需要特定的RDD,则还可以显式释放它。
了解数据框和数据集
DataFrame类似于关系数据库中的表,pandas数据框架或R中的数据框架。它是将行组织成列的分布式集合。它使用RDD的不变,内存中,弹性,分布式和并行功能,并将架构应用于数据。 DataFrames也被延迟评估。此外,它们为分布式数据操作提供了特定于域的语言(DSL)。
从概念上讲,DataFrame是通用对象Dataset [Row]的集合的别名,其中行是通用无类型对象。这意味着在编译阶段会捕获到DataFrames的语法错误。但是,仅在运行时才检测到分析错误。
DataFrame可以从多种来源构造,例如结构化数据文件,Hive表,数据库或RDD。可以从本地文件系统,HDFS,Amazon S3和RDBMS中读取源数据。此外,还支持其他流行的数据格式,例如CSV,JSON,Avro,Parquet等。此外,您还可以创建和使用自定义数据源。
DataFrame API支持Scala,Java,Python和R编程API。 DataFrames API是声明性的,并且与过程Spark代码结合使用,它在应用程序中的关系和过程处理之间提供了更紧密的集成。可以使用Spark的过程API或关系API(具有更丰富的优化)来操纵DataFrame。
在早期版本的Spark中,您必须编写在RDD上运行的任意Java,Python或Scala函数。在这种情况下,这些函数在不透明的Java对象上执行。因此,用户功能本质上是使用不透明对象和数据类型执行不透明计算的黑盒子。这种方法非常通用,此类程序可以完全控制每个数据操作的执行。
但是,由于引擎不知道您正在执行的代码或数据的性质,因此无法优化这些任意的Java对象。此外,开发人员有责任编写高效的程序,这些程序取决于其特定工作负载的性质。
在Spark 2.0中,使用SQL,DataFrames和Datasets的主要好处是,使用这些高级编程接口进行编程更容易,同时自动获得了提高性能的好处。您只需编写更少的代码行,程序就会自动优化,并为您生成高效的代码。这样可以提高性能,同时大大减轻开发人员的负担。现在,开发人员可以专注于需要完成的事情的“内容”而不是“如何”。
Dataset API首先添加到Spark 1.6中,以提供RDD和Spark SQL的优化器的优点。可以从JVM对象构造一个数据集,然后使用诸如map,filter等功能转换来操纵它。由于数据集是使用用户定义的案例类指定的强类型对象的集合,因此可以在编译时检测语法错误和分析错误。
统一的数据集API可以在Scala和Java中使用。但是,Python不支持Dataset API
在下面的示例中,我们介绍了一些基本的DataFrame / Dataset操作。 为此,我们将使用两个在重复记录检测和记录链接应用程序中通常使用的餐厅清单数据集。 这两个列表,分别来自Zagat和Fodor的餐厅指南,在它们之间有重复的记录。 为使此示例简单,我们将输入文件手动转换为CSV格式。 您可以从http://www.cs.utexas.edu/users/ml/riddle/data.html下载原始数据集。
需要手动转换成csv是,操,这有点操蛋,需要对
case class RestClass(name: String, street: String, city: String, phone: String, cuisine: String)
//接下来,我们从两个文件创建数据集:
val rest1DS = spark.sparkContext.textFile("file:///Users/aurobindosarkar/Documents/SparkBook/data/zagats.csv") .map(_.split(",") ) .map(attributes =>
RestClass(attributes(0) .trim, attributes(1) .trim, attributes(2) .trim, attributes(3) .trim, attributes(4) .trim) ) .toDS()
val rest2DS = spark.sparkContext.textFile("file:///Users/aurobindosarkar/Documents/SparkBook/data/fodors.csv") .map(_.split(",") ) .map(attributes =>
RestClass(attributes(0) .trim, attributes(1) .trim, attributes(2) .trim, attributes(3) .trim, attributes(4) .trim) ) .toDS()
//我们定义一个UDF来清理和转换第二个数据集中的电话号码以匹配格式
def formatPhoneNo(s: String) : String = s match {case s if s.contains("/") => s.replaceAll("/", "-") .replaceAll("- ", "-") .replaceAll("--", "-") case _ => s
}
val udfStandardizePhoneNos = udf[String, String] ( x => formatPhoneNo(x) )
val rest2DSM1 = rest2DS.withColumn("stdphone", udfStandardizePhoneNos(rest2DS.col("phone") )
//接下来,我们根据数据集创建临时视图
rest1DS.createOrReplaceTempView("rest1Table")
rest2DSM1.createOrReplaceTempView("rest2Table")
//通过在这些表上执行一条SQL语句来返回重复次数的计数,该语句返回具有匹配电话号码的记录数的计数:
spark.sql("SELECT count(*) from rest1Table, rest2Table where rest1Table.phone = rest2Table.stdphone") .show()
//接下来,我们从数据集中创建临时视图:
rest1DS.createOrReplaceTempView("rest1Table")
rest2DSM1.createOrReplaceTempView("rest2Table")
//通过在这些表上执行一条SQL语句来返回重复次数的计数,该语句返回具有匹配电话号码的记录数的计数:
spark.sql("SELECT count(*) from rest1Table, rest2Table where rest1Table.phone = rest2Table.stdphone") .show()
了解Catalyst优化器
Catalyst优化器是Spark SQL的核心,并在Scala中实现。 它启用了几个关键功能,例如模式推断(来自JSON数据),这些功能在数据分析工作中非常有用。 下图显示了从包含DataFrames / Datasets的开发人员程序到最终执行计划的高级转换过程:
dataframe/dataset ==> 逻辑计划 ===> 策划人 ==> 执行计划
程序的内部表示是一个查询计划。 查询计划描述与查询中定义的数据操作匹配的数据操作,例如聚合,联接和过滤器。 这些操作从输入数据集生成一个新的数据集。 在准备好查询计划的初始版本之后,Catalyst优化器将应用一系列转换将其转换为优化的查询计划。 最后,Spark SQL代码生成机制将优化的查询计划转换为准备执行的RDD的DAG。 查询计划和优化的查询计划在内部表示为树。 因此,Catalyst优化器的核心是一个通用库,用于表示树并应用规则来对其进行操作。 在此库之上,还有其他几个更专门用于关系查询处理的库。
Catalyst有两种类型的查询计划:逻辑计划和物理计划。
逻辑计划在不定义如何执行特定计算的情况下描述了数据集上的计算。 通常,策略计划在生成的行的一组约束下生成属性或列的列表作为输出。 物理计划描述了数据集上的计算,并带有关于如何执行它们的特定定义(它是可执行的)。
让我们更详细地探讨转换步骤。 初始查询lan本质上是一个未解决的逻辑计划,也就是说,在此阶段,我们不知道数据集或列(包含在数据集中)的来源,也不知道列的类型。 该流程的第一步是分析步骤。 在分析期间,目录信息用于将未解决的逻辑计划转换为已解决的逻辑计划。
下一步,将一组逻辑优化规则应用于已解析的逻辑计划,从而生成优化的逻辑计划。 下一步,优化器可能会生成多个物理计划,并比较它们的成本以选择最佳计划。 基于Spark SQL的基于成本的优化器(CBO)的第一个版本已在Spark 2.2中发布。 有关基于成本的优化的更多详细信息,请参见第11章“调整Spark SQL组件的性能”。
下图所示,这三个(DataFrame,Dataset和SQL)共享相同的优化管道:
了解Catalyst优化
在Catalyst中,有两种主要的优化类型:逻辑和物理:
逻辑优化:这包括优化器将过滤谓词下推到数据源并允许执行跳过无关数据的能力。例如,对于Parquet文件,可以跳过整个块,并可以通过字典编码将字符串的比较转换为更便宜的整数比较。对于RDBMS,将谓词下推到数据库以减少数据流量。
物理优化:包括在广播连接和随机连接之间进行智能选择以减少网络流量,执行较低级别的优化(例如消除昂贵的对象分配和减少虚拟函数调用)的能力。因此,当在程序中引入DataFrame时,性能通常会提高。
规则执行器负责分析和逻辑优化步骤,而一组策略和规则执行器负责物理规划步骤。规则执行器通过批量应用一组规则将一棵树转换为另一棵相同类型的树。这些规则可以应用一次或多次。同样,这些规则中的每一个都实现为转换。转换基本上是与每棵树相关联的功能,并用于实现单个规则。用Scala术语,将转换定义为部分函数(为可能参数的子集定义的函数)。这些通常定义为case语句,以确定是否为给定输入定义了部分功能(使用模式匹配)。
规则执行器通过准备标量子查询,确保输入行符合特定操作的要求并应用物理优化,使物理计划准备好执行。例如,在排序合并联接操作中,需要根据联接条件对输入行进行排序。在执行排序合并联接操作之前,优化器将根据需要在输入行上插入适当的排序操作。
了解催化剂转化
从概念上讲,Catalyst优化器执行两种类型的转换。第一个将输入树类型转换为相同的树类型(即,不更改树类型)。这种类型的转换包括将一个表达式转换为另一种表达式,将一个逻辑计划转换为另一个逻辑计划,以及将一个物理计划转换为另一个物理计划。第二种类型的转换将一种树类型转换为另一种类型,例如,从逻辑计划转换为物理计划。通过应用一组策略,可以将逻辑计划转换为物理计划。这些策略使用模式匹配将树转换为其他类型。例如,我们具有用于将逻辑项目和过滤器运算符分别匹配到物理项目和过滤器运算符的特定模式。
一组规则也可以组合为一个规则以完成特定的转换。例如,根据您的查询,可以在执行联接操作之前将诸如filter之类的谓词下推以减少总行数。此外,如果查询中的查询中包含常量,则常量折叠优化将在编译时计算一次该表达式,而不是在运行时对每一行重复该表达式。此外,如果查询需要列的子集,则列修剪可以帮助将列减少为必不可少的列。所有这些规则可以组合为一个规则,以实现所有三个转换。
在以下示例中,我们测量了Spark 1.6和Spark 2.2的执行时间差异。 在下一个示例中,我们将iPinYou实时出价数据集用于计算广告研究。 该数据集包含来自iPinYou全球RTB出价算法竞赛三个季节的数据。 您可以从伦敦大学学院的数据服务器http://data.computational-advertising.org/下载此数据集。
Introducing Project Tungsten
Understanding Catalyst transformations
Understanding Catalyst optimizations ---这块内容,需要重看
在流应用程序中使用Spark SQL
流应用程序变得越来越复杂,因为这样的计算并非孤立地运行。 他们需要与批处理数据进行交互,支持交互分析,支持复杂的机器学习应用程序等。 通常,此类应用程序将传入事件流存储在长期存储中,连续监视事件,并对存储的数据运行机器学习模型,同时启用传入流的连续学习。 它们还具有交互式查询存储数据的能力,同时提供了一次精确的写入保证,处理迟到的数据,执行聚合等。 这些类型的应用程序不仅仅是流应用程序,因此被称为连续应用程序。
在Spark 2.0之前,流应用程序是基于DStreams的概念构建的。 使用DStreams有几个痛点。 在DStreams中,时间戳记是事件实际进入Spark系统的时间。 没有考虑事件中嵌入的时间。 另外,尽管同一个引擎可以处理批处理和流计算,但是所涉及的API尽管在RDD(批处理)和DStream(流)之间相似,但要求开发人员进行代码更改。 DStream流模型使开发人员不得不承担各种失败条件的负担,并且很难推理出数据一致性问题。 在Spark 2.0中,引入了结构化流处理所有这些痛点。
结构化流是一种快速的,容错的,一次准确的状态流处理方法。它启用了流分析,而不必考虑流的底层机制。在新模型中,可以将输入视为仅附加表(连续增长)中的数据。触发器指定了检查输入是否有新数据到达的时间间隔。如下图所示,查询代表查询或输入上的映射,过滤和归约等操作,结果代表根据指定操作在每个触发间隔中更新的最终表。输出定义了每个时间间隔中要写入数据接收器的结果部分。输出模式可以是complete,delta或append,其中complete输出模式意味着每次都写入完整的结果表,delta输出模式仅写入前一批中已更改的行,append输出模式仅写入新行,分别: