Spark入门实战系列Word格式.docx
《Spark入门实战系列Word格式.docx》由会员分享,可在线阅读,更多相关《Spark入门实战系列Word格式.docx(31页珍藏版)》请在冰豆网上搜索。
1.3
SparkSQL的性能
Shark的出现,使得SQL-on-Hadoop的性能比Hive有了10-100倍的提高:
那么,摆脱了Hive的限制,SparkSQL的性能又有怎么样的表现呢?
虽然没有Shark相对于Hive那样瞩目地性能提升,但也表现得非常优异:
为什么SparkSQL的性能会得到怎么大的提升呢?
主要SparkSQL在下面几点做了优化:
A:
内存列存储(In-MemoryColumnarStorage)
SparkSQL的表数据在内存中存储不是采用原生态的JVM对象存储方式,而是采用内存列存储,如下图所示。
该存储方式无论在空间占用量和读取吞吐率上都占有很大优势。
对于原生态的JVM对象存储方式,每个对象通常要增加12-16字节的额外开销,对于一个270MB的TPC-Hlineitemtable数据,使用这种方式读入内存,要使用970MB左右的内存空间(通常是2~5倍于原生数据空间);
另外,使用这种方式,每个数据记录产生一个JVM对象,如果是大小为200B的数据记录,32G的堆栈将产生1.6亿个对象,这么多的对象,对于GC来说,可能要消耗几分钟的时间来处理(JVM的垃圾收集时间与堆栈中的对象数量呈线性相关)。
显然这种内存存储方式对于基于内存计算的Spark来说,很昂贵也负担不起。
对于内存列存储来说,将所有原生数据类型的列采用原生数组来存储,将Hive支持的复杂数据类型(如array、map等)先序化后并接成一个字节数组来存储。
这样,每个列创建一个JVM对象,从而导致可以快速的GC和紧凑的数据存储;
额外的,还可以使用低廉CPU开销的高效压缩方法(如字典编码、行长度编码等压缩方法)降低内存开销;
更有趣的是,对于分析查询中频繁使用的聚合特定列,性能会得到很大的提高,原因就是这些列的数据放在一起,更容易读入内存进行计算。
B:
字节码生成技术(bytecodegeneration,即CG)
在数据库查询中有一个昂贵的操作是查询语句中的表达式,主要是由于JVM的内存模型引起的。
比如如下一个查询:
SELECTa+bFROMtable
在这个查询里,如果采用通用的SQL语法途径去处理,会先生成一个表达式树(有两个节点的Add树,参考后面章节),在物理处理这个表达式树的时候,将会如图所示的7个步骤:
1.
调用虚函数Add.eval(),需要确认Add两边的数据类型
2.
调用虚函数a.eval(),需要确认a的数据类型
3.
确定a的数据类型是Int,装箱
4.
调用虚函数b.eval(),需要确认b的数据类型
5.
确定b的数据类型是Int,装箱
6.
调用Int类型的Add
7.
返回装箱后的计算结果
其中多次涉及到虚函数的调用,虚函数的调用会打断CPU的正常流水线处理,减缓执行。
Spark1.1.0在catalyst模块的expressions增加了codegen模块,如果使用动态字节码生成技术(配置spark.sql.codegen参数),SparkSQL在执行物理计划的时候,对匹配的表达式采用特定的代码,动态编译,然后运行。
如上例子,匹配到Add方法:
然后,通过调用,最终调用:
最终实现效果类似如下伪代码:
vala:
Int=inputRow.getInt(0)
valb:
Int=inputRow.getInt
(1)
valresult:
Int=a+b
resultRow.setInt(0,result)
对于Spark1.1.0,对SQL表达式都作了CG优化,具体可以参看codegen模块。
CG优化的实现主要还是依靠scala2.10的运行时放射机制(runtimereflection)。
对于SQL查询的CG优化,可以简单地用下图来表示:
C:
Scala代码优化
另外,SparkSQL在使用Scala编写代码的时候,尽量避免低效的、容易GC的代码;
尽管增加了编写代码的难度,但对于用户来说,还是使用统一的接口,没受到使用上的困难。
下图是一个Scala代码优化的示意图:
2、
SparkSQL运行架构
类似于关系型数据库,SparkSQL也是语句也是由Projection(a1,a2,a3)、DataSource(tableA)、Filter(condition)组成,分别对应sql查询过程中的Result、DataSource、Operation,也就是说SQL语句按Result-->
DataSource-->
Operation的次序来描述的。
当执行SparkSQL语句的顺序为:
1.对读入的SQL语句进行解析(Parse),分辨出SQL语句中哪些词是关键词(如SELECT、FROM、WHERE),哪些是表达式、哪些是Projection、哪些是DataSource等,从而判断SQL语句是否规范;
2.将SQL语句和数据库的数据字典(列、表、视图等等)进行绑定(Bind),如果相关的Projection、DataSource等都是存在的话,就表示这个SQL语句是可以执行的;
3.一般的数据库会提供几个执行计划,这些计划一般都有运行统计数据,数据库会在这些计划中选择一个最优计划(Optimize);
4.计划执行(Execute),按Operation-->
Result的次序来进行的,在执行过程有时候甚至不需要读取物理表就可以返回结果,比如重新运行刚运行过的SQL语句,可能直接从数据库的缓冲池中获取返回结果。
2.1
Tree和Rule
SparkSQL对SQL语句的处理和关系型数据库对SQL语句的处理采用了类似的方法,首先会将SQL语句进行解析(Parse),然后形成一个Tree,在后续的如绑定、优化等处理过程都是对Tree的操作,而操作的方法是采用Rule,通过模式匹配,对不同类型的节点采用不同的操作。
在整个sql语句的处理过程中,Tree和Rule相互配合,完成了解析、绑定(在SparkSQL中称为Analysis)、优化、物理计划等过程,最终生成可以执行的物理计划。
2.1.1
Tree
Tree的相关代码定义在sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/trees
LogicalPlans、Expressions、PhysicalOperators都可以使用Tree表示
Tree的具体操作是通过TreeNode来实现的
Ø
SparkSQL定义了catalyst.trees的日志,通过这个日志可以形象的表示出树的结构
TreeNode可以使用scala的集合操作方法(如foreach,map,flatMap,collect等)进行操作
有了TreeNode,通过Tree中各个TreeNode之间的关系,可以对Tree进行遍历操作,如使用transformDown、transformUp将Rule应用到给定的树段,然后用结果替代旧的树段;
也可以使用transformChildrenDown、transformChildrenUp对一个给定的节点进行操作,通过迭代将Rule应用到该节点以及子节点。
TreeNode可以细分成三种类型的Node:
UnaryNode
一元节点,即只有一个子节点。
如Limit、Filter操作
BinaryNode
二元节点,即有左右子节点的二叉节点。
如Jion、Union操作
LeafNode
叶子节点,没有子节点的节点。
主要用户命令类操作,如SetCommand
2.1.2
Rule
Rule的相关代码定义在sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/rules
Rule在SparkSQL的Analyzer、Optimizer、SparkPlan等各个组件中都有应用到
Rule是一个抽象类,具体的Rule实现是通过RuleExecutor完成
Rule通过定义batch和batchs,可以简便的、模块化地对Tree进行transform操作
Rule通过定义Once和FixedPoint,可以对Tree进行一次操作或多次操作(如对某些Tree进行多次迭代操作的时候,达到FixedPoint次数迭代或达到前后两次的树结构没变化才停止操作,具体参看RuleExecutor.apply)
2.2
sqlContext和hiveContext的运行过程
SparkSQL有两个分支,sqlContext和hiveContext,sqlContext现在只支持SQL语法解析器(SQL-92语法);
hiveContext现在支持SQL语法解析器和hivesql语法解析器,默认为hiveSQL语法解析器,用户可以通过配置切换成SQL语法解析器,来运行hiveSQL不支持的语法,
2.2.1
sqlContext的运行过程
sqlContext总的一个过程如下图所示:
1.SQL语句经过SqlParse解析成UnresolvedLogicalPlan;
2.使用analyzer结合数据数据字典(catalog)进行绑定,生成resolvedLogicalPlan;
3.使用optimizer对resolvedLogicalPlan进行优化,生成optimizedLogicalPlan;
4.使用SparkPlan将LogicalPlan转换成PhysicalPlan;
5.使用prepareForExecution()将PhysicalPlan转换成可执行物理计划;
6.使用execute()执行可执行物理计划;
7.生成SchemaRDD。
在整个运行过程中涉及到多个SparkSQL的组件,如SqlParse、analyzer、optimizer、SparkPlan等等
2.2.2hiveContext的运行过程
hiveContext总的一个过程如下图所示:
1.SQL语句经过HiveQl.parseSql解析成UnresolvedLogicalPlan,在这个解析过程中对hiveql语句使用getAst()获取AST树,然后再进行解析;
2.使用analyzer结合数据hive源数据Metastore(新的catalog)进行绑定,生成resolvedLogicalPlan;
3.使用optimizer对resolvedLogicalPlan进行优化,生成optimizedLogicalPlan,优化前使用了ExtractPythonUdfs(catalog.PreInsertionCasts(catalog.CreateTables(analyzed)))进行预处理;
4.使用hivePlanner将LogicalPlan转换成PhysicalPlan;
7.执行后,使用map(_.copy)将结果导入SchemaRDD。
2.3
catalyst优化器
SparkSQL1.1总体上由四个模块组成:
core、catalyst、hive、hive-Thriftserver:
core处理数据的输入输出,从不同的数据源获取数据(RDD、Parquet、json等),将查询结果输出成schemaRDD;
catalyst处理查询语句的整个处理过程,包括解析、绑定、优化、物理计划等,说其是优化器,还不如说是查询引擎;
hive对hive数据的处理
hive-ThriftServer提供CLI和JDBC/ODBC接口
在这四个模块中,catalyst处于最核心的部分,其性能优劣将影响整体的性能。
由于发展时间尚短,还有很多不足的地方,但其插件式的设计,为未来的发展留下了很大的空间。
下面是catalyst的一个设计图:
其中虚线部分是以后版本要实现的功能,实线部分是已经实现的功能。
从上图看,catalyst主要的实现组件有:
●sqlParse,完成sql语句的语法解析功能,目前只提供了一个简单的sql解析器;
●Analyzer,主要完成绑定工作,将不同来源的UnresolvedLogicalPlan和数据元数据(如hivemetastore、Schemacatalog)进行绑定,生成resolvedLogicalPlan;
●optimizer对resolvedLogicalPlan进行优化,生成optimizedLogicalPlan;
Planner将LogicalPlan转换成PhysicalPlan;
CostModel,主要根据过去的性能统计数据,选择最佳的物理执行计划
这些组件的基本实现方法:
先将sql语句通过解析生成Tree,然后在不同阶段使用不同的Rule应用到Tree上,通过转换完成各个组件的功能。
Analyzer使用AnalysisRules,配合数据元数据(如hivemetastore、Schemacatalog),完善UnresolvedLogicalPlan的属性而转换成resolvedLogicalPlan;
optimizer使用OptimizationRules,对resolvedLogicalPlan进行合并、列裁剪、过滤器下推等优化作业而转换成optimizedLogicalPlan;
Planner使用PlanningStrategies,对optimizedLogicalPlan
3、SparkSQLCLI
CLI(Command-LineInterface,命令行界面)是指可在用户提示符下键入可执行指令的界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后予以执行。
SparkCLI指的是使用命令界面直接输入SQL命令,然后发送到Spark集群进行执行,在界面中显示运行过程和最终的结果。
Spark1.1相较于Spark1.0最大的差别就在于Spark1.1增加了SparkSQLCLI和ThriftServer,使得Hive用户还有用惯了命令行的RDBMS数据库管理员较容易地上手,真正意义上进入了SQL时代。
【注】SparkCLI和SparkThriftServer实验环境为第二课《Spark编译与部署(下)--Spark编译安装》所搭建
3.1
运行环境说明
3.1.1
硬软件环境
主机操作系统:
Windows64位,双核4线程,主频2.2G,10G内存
虚拟软件:
VMware®
Workstation9.0.0build-812388
虚拟机操作系统:
CentOS64位,单核
虚拟机运行环境:
JDK:
1.7.0_5564位
Hadoop:
2.2.0(需要编译为64位)
Scala:
2.11.4
Spark:
1.1.0(需要编译)
Hive:
0.13.1
3.1.2
机器网络环境
集群包含三个节点,节点之间可以免密码SSH访问,节点IP地址和主机名分布如下:
序号
IP地址
机器名
类型
核数/内存
用户名
1
192.168.0.61
hadoop1
NN/DN/RM
Master/Worker
1核/3G
hadoop
/app
程序所在路径
/app/scala-...
/app/hadoop
/app/complied
2
192.168.0.62
hadoop2
DN/NM/Worker
1核/2G
3
192.168.0.63
hadoop3
3.2
配置并启动
3.2.1
创建并配置hive-site.xml
在运行SparkSQLCLI中需要使用到HiveMetastore,故需要在Spark中添加其uris。
具体方法是在SPARK_HOME/conf目录下创建hive-site.xml文件,然后在该配置文件中,添加hive.metastore.uris属性,具体如下:
<
configuration>
property>
name>
hive.metastore.uris<
/name>
value>
thrift:
//hadoop1:
9083<
/value>
description>
ThriftURIfortheremotemetastore.Usedbymetastoreclienttoconnecttoremotemetastore.<
/description>
/property>
/configuration>
3.2.2
启动Hive
在使用SparkSQLCLI之前需要启动HiveMetastore(如果数据存放在HDFS文件系统,还需要启动Hadoop的HDFS),使用如下命令可以使HiveMetastore启动后运行在后台,可以通过jobs查询:
$nohuphive--servicemetastore>
metastore.log2>
&
1&
3.2.3
启动Spark集群和SparkSQLCLI
通过如下命令启动Spark集群和SparkSQLCLI:
$cd/app/hadoop/spark-1.1.0
$sbin/start-all.sh
$bin/spark-sql--masterspark:
7077--executor-memory1g
在集群监控页面可以看到启动了SparkSQL应用程序:
这时就可以使用HQL语句对Hive数据进行查询,另外可以使用COMMAND,如使用set进行设置参数:
默认情况下,SparkSQLShuffle的时候是200个partition,可以使用如下命令修改该参数:
SETspark.sql.shuffle.partitions=20;
运行同一个查询语句,参数改变后,Task(partition)的数量就由200变成了20。
3.2.4
命令参数
通过bin/spark-sql--help可以查看CLI命令参数:
其中[options]
是CLI启动一个SparkSQL应用程序的参数,如果不设置--master的话,将在启动spark-sql的机器以local方式运行,只能通过http:
//机器名:
4040进行监控;
这部分参数,可以参照Spark1.0.0
应用程序部署工具spark-submit
的参数。
[clioption]是CLI的参数,通过这些参数CLI可以直接运行SQL文件、进入命令行运行SQL命令等等,类似以前的Shark的用法。
需要注意的是CLI不是使用JDBC连接,所以不能连接到ThriftServer;
但可以配置conf/hive-site.xml连接到Hive的Metastore,然后对Hive数据进行查询。
3.3
实战SparkSQLCLI
3.3.1
获取订单每年的销售单数、销售总额
第一步
设置任务个数,在这里修改为20个
spark-sql>
第二步
运行SQL语句
usehive;
selectc.theyear,count(distincta.ordernumber),sum(b.amount)fromtbStockajointbStockDetail
bona.ordernumber=b.ordernumberjointbDatecona.dateid=c.dateidgroupbyc.theyearorderbyc.theyear;
第三步
查看运行结果
3.3.2
计算所有订单每年的总金额
执行SQL语句
selectc.theyear,count(di