Spark-手记-5 --最佳实践


因为大数据计算场景下,系统的瓶颈不在于计算,而是网络传输和磁盘读写。
此时CPU反而不是瓶颈,所以无论Hadoop和Spark都在这方面做了大量优化。
比如:Hadoop和Spark中最重要的shuffle机制,就是为解决网络传输而设计的,并且不断优化,
每次优化都可以在整体上提升性能。
Hadoop和Spark这类通用计算平台都遵循一个理念,就是"宁可移动计算,不要移动数据"。


Spark可以方便地进行交互式编程:
$SPARK_HOME/bin/spark-shell --master local[2]

--master:用于指定Master服务器的地址
local:表示运行在本地
[2]:可选项,表示启动2个工作线程


Hadoop从2.0开始,把存储和计算分离开来,形成两个相对独立的子集群:HDFS和YARN,MapReduce依附于YARN来运行。
Hadoop HDFS是一个分布式文件系统,为集群提供大文件的存储能力。
NameNode管理所有的文件信息,一主一备,通过Zookeeper来实现容灾切换,
QJM节点的数量为:2n+1,负责记录NameNode的流水日志,确保数据不丢失,
DataNode担负集群的存储负载,用于数据存储。

Hadoop YARN为MapReduce计算提供调度服务,而且也可以为其他符合YARN编程接口要求的集群提供调度服务,
这也是Spark可以借助YARN来做资源调度的前提。
该集群有两类节点:ResourceManager 和 NodeManager,其结构与Spark集群非常相似,
ResourceManager为一主多备,
NodeManager一般与DataNode部署在一起,实现最高效的数据访问。

考虑到无论是Spark计算还是Hadoop MapReduce计算,
离数据越近,移动数据的开销越小,
而且在大数据计算的大部分场景下,移动数据的开销都会大于计算的开销。

 

存储
Spark Slave节点尽可能靠近存储系统(如:HDFS、HBase),
如果不能部署在相同节点上那么请尽可能让它们在物理位置上更进一些以减少网络IO的开销。
比如:尽可能在相同机架上、同一路由器下、相同的机房里。


调度管理
就像普通程序在运行时由操作系统来统一调度分配可以使用使用的CPU、内存资源一样,
Spark集群上的程序要想申请使用资源,也要由统一的系统来调度分配,这就是调度管理系统。

如果集群上只有一个程序在运行,那么也没有必要进行调度了,
如果多个程序在运行,就存在资源竞争的问题,所以调度管理最主要的目的是资源分配。

Spark集群上的资源主要是指CPU Core数量和物理内存。
Core数量一般是物理CPU核的数量,我们也可以将其设置为CPU核数量的2倍或更多。
在程序运行时,每个Core对应一个线程。
物理内存即所有机器节点分配给Spark集群的内存总量,
一般每个节点最多会将80%的内存资源分配给Spark集群使用。

Spark自身支持资源调度,Standalone模式下由Spark集群中的Master节点类进行资源调度。
此外,Spark还支持与其他调度管理系统一起工作,目前支持YARN和Mesos。
这样的好处是:外部调度系统可能支持更强大、灵活的调度策略。


集群管理器
集群管理器负责集群的资源调度。

一、Standalone模式(资源管理器是Master节点)
最简单的一种管理资源,不依赖于其他系统,调度策略相对单一,只支持先进先出模式(FIFO,First-In-Frist-Out)。

二、YARN(资源管理器是YARN集群)
它主要用来管理资源。
YARN支持动态资源管理,更适合多用户场景下的集群管理,
而且YARN可以同时调度Spark计算和Hadoop MapReduce计算,还可以调度其他实现了YARN调度接口的集群计算,
非常适用于多个集群同时部署的场景,是目前最主流的一种资源管理系统。


共享变量
Spark程序的大部分操作都是RDD操作,通过传入函数给RDD操作函数来计算。
这些函数在不同的节点上并发执行,内部的变量 有不同的作用域,不能互相访问,有些不大方便,
所以Spark提供了两类共享变量供编程使用---广播变量 和 计数器。

一、广播变量
这是一个只读对象,在所有节点上都有一份缓存,
创建方法是:SparkContext.broadcast()
val data = sc.broadcast(Array(1,2,3));


容错体系
Spark将计算转换为一个有向无环图(DAG)的任务集合,这样可以通过重复执行DAG里的一部分任务来完成容错恢复。
但是,由于主要的数据存储在分布式文件系统中,没有其他存储概念,
容错过程需要在网络上进行数据复制,从而增加了大量的消耗。
所以,分布式编程中经常需要检查点,即:将某个时机的中间数据写到存储中(通常是:HDFS 分布式文件系统)


Spark程序配置管理
Spark对程序提供了非常灵活的配置方式,
可以使用:环境变量、配置文件、命令行参数,还可以直接在Spark程序中指定。
不同的配置方式有不同的优先级,可以互相覆盖。
而且这些配置的属性在Web界面直接看到,非常方便我们管理配置。

 

Spark程序配置加载过程
Spark程序一般都是由脚本 $SPARK_HOME/bin/spark-submit 来提交,
交互式编程 $SPARK_HOME/bin/spark-shell 其实也是通过它来提交的。
通过这种方式启动的Spark程序的加载配置过程如下:
1、设置 $SPARK_HOME 的值为 bin/spark-submit 脚本所在目录的上一级目录
2、计算配置文件目录,从环境变量 $SPARK_CONF_DIR中读取。如果没有设置,则取默认值:${SPARK_HOME}/conf
3、执行配置文件目录下的shell脚本配置文件 spark-env.sh,设置基本的环境变量。
4、加载配置文件目录下的默认配置文件spark-defaults.conf。
5、读取命令行参数,覆盖前面的默认配置。
6、使用SparkConf对象中的选项,覆盖前面的配置。

 

环境变量配置
少量基础的Spark程序配置可以通过环境变量的方式来指定,
比如:配置文件目录是通过环境变量$SPARK_CONF_DIR来指定的,其默认值是:$SPARK_HOME/conf
我们可以在提交Spark程序之前通过指定 SPARK_CONF_DIR值的方式来使用其他目录作为配置文件的目录(一般不这样做)。

环境变量可以在提交程序之前通过 export 的方式设置,
也可以在配置文件目录下的 spark-env.sh文件中指定,其中spark-env.sh本身也是一个脚本。

 

Spark属性项配置优先级
1、SparkConf对象
2、spark-submit命令行参数
3、spark-defauults.conf 配置文件

val conf = new SparkConf();
val sc = new SparkContext(conf);


Shuffle
Shuffle的概念来自Hadoop的MapReduce计算过程。
当对一个RDD的某个分区进行操作而无法精确知道依赖前一个RDD的哪个分区时,依赖关系变成了前一个RDD的所有分区。
比如,几乎所有<key,value>类型的RDD操作,都涉及按Key对RDD成员进行重组,
将具有相同Key但分布在不同节点上的成员聚合到一个节点上,以便对它们的value进行操作。
这个重组的过程就是Shuffle操作。
因为Shuffle操作涉及数据的传输,所以成本特别高,而且过程复杂。

Shuffle是一个非常消耗资源的操作,
除了会涉及大量网络IO操作并使用大量的内存外,还会在磁盘上生成大量临时文件,以避免错误恢复时重新计算。
因为Shuffle操作的结果其实是一次调度的Stage的结果,而一次Stage包含许多Task,缓存下来还是很划算的。
Shuffle使用的本地磁盘目录由 spark.local.dir 属性指定。


DAG调度与Task调度的区别
DAG是最高层级的调度,为每个Job绘制出一个有向无环图(DAG),跟踪各Stage的输出,计算完成Job的最短路径,
并将Task提交给Task调度器来执行。
Task调度器只负责接受只负责接受DAG调度器的请求,负责Task的实际调度执行,
所以DAGScheduler的初始化必须在Task调度器之后。

 

DAG与Task这种分离设计的好处
Spark可以灵活设计自己的DAG调度,同时还能与其他资源调度系统结合,比如:YARN、Mesos。

SparkContext在初始化时,创建了DAG调度与Task调度来负责RDDAction操作的调度执行。


DAGScheduler
DAGScheduler负责Spark的最高级别的任务调度,调度的粒度是Stage,
它为每个Job的所有Stage计算一个有向无环图(DAG),控制它们的并发,并找到一个最佳路径来执行它们。
具体的执行过程是将Stage下的Task集提交给TaskScheduler对象,
由它来提交到集群上去申请资源并最终完成执行。

 

Stage
值得注意的是,
仅对依赖类型是ShuffleDependency(宽依赖)的RDD操作创建Stage,其他的RDD操作并没有创建Stage。
窄依赖,一个RDD分区只依赖上一个RDD的部分分区,而且这些分区都在相同的节点上。
宽依赖(Shuffle依赖),一个RDD分区可能会依赖上一级RDD的全部分区,一个典型的例子是:groupBy聚合操作。
这两类操作在计算上有明显的区别,
窄依赖都在同一个节点上进行计算。
Shuffle依赖(宽依赖)跨越多个节点,甚至所有涉及的计算节点。
因此,DAG在调度时,对于在相同节点上进行Task计算,会合并为一个Stage(窄依赖)。

所以,只有两种情况下会生成新的Stage:
一类是依赖类型是Shuffle的转换操作会触发生成新的Stage,几乎所有的ByKey操作都是,比如:groupByKey、reduceByKey。
另一类是Action操作,是为了生成默认的Stage,这样即便没有Shuffle类操作,保证至少有一个Stage。

 


Spark SQL专门用于处理结构化数据。
Spark SQL的最大优势是性能非常高,这得益于Spark的基因,
而且还使用了基于成本的优化器、列存储、代码生成...等技术。
此外,Spark SQL也可以扩展到上千个计算节点以及数小时的计算能力,并且支持自动容错恢复。

Spark SQL提供了SQL交互式查询功能,还支持业界标准的JDBC/ODBC访问接口,
这样很容易用Spark SQL来实现分布式数据仓库,以及商业智能计算。
而且Spark SQL与Hive基本完全兼容,我们可以像使用Hive一样来使用Spark SQL。


分布式SQL引擎
Spark SQL作为分布式引擎时,有两种运行方式:
一、JDBC/ODBC Server
二、Spark SQL命令行


JDBC/ODBC服务的部署结构,除了部署Spark集群之外,还需要部署一个DB用于存储数据库的元数据。
在启动JDBC/ODBC之前,需要准备一下配置文件。
JDBC/ODBC Server是基于HiveServer2来实现的,所以JDBC/ODBC的配置方法与HiveServer2的配置方法基本相同。
如果是与现有的Hive一起工作直接使用Hive的配置文件(hive-site.xml)即可,或者新建一个,
在 $SPARK_HOME/conf 目录下准备一个名为:hive-site.xml 的配置文件
vim hive-site.xml
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.cj.jdbc.Driver</value>

javax.jdo.option.ConnectionURL
javax.jdo.option.ConnectionUserName
javax.jdo.option.ConnectionPassword
....

hive.server2.thrift.bind.host (localhost)
hive.server2.thrift.port (10000)


启动JDBC/ODBC Server
$SPARK_HOME/sbin/start-thriftserver.sh

使用beeline交互工具
JDBC/ODBC Server启动之后,我们可以用beeline来测试启动是否正常
$SPARK_HOME/bin/beeline
beeline> !connect jdbc:hive2://localhost:10000

也可以在启动beeline时,直接指定JDBC server
$SPARK_HOME/bin/beeline -u 'jdbc:hive2://localhost:10000'


运行Spark SQL 命令行界面(CLI)
Spark SQL CLI 是另一种运行Spark SQL的方式,
使用它可以在本地直接启动服务,这非常适合调试使用。
Spark SQL 与 Thrift JDBC服务是不同的。
要启动Spark SQL CLI,只需要执行如下命令,不需要像JDBC/ODBC那样先启动一个额外的服务。
$SPARK_HOME/bin/spark-sql

Spark SQL CLI也支持与JDBC/ODBC Server完全一样的配置。
使用 $SPARK_HOME/bin/spark-sql --help 命令,可以查看所有的选项列表。

 

查询使用的SQL支持使用不同的SQL语法解析器。
默认情况下,会使用SQLContext中自带的一个简单的SQL语法解析器。
如果不够用,可以换成功能更强大的HiveQL解析器。
可以使用 spark.sql.dialect 选项重新指定,设置成:hiveql,
可以通过 setConf()方法设置,或者在SQL中使用 set key=value 命令来设置。


性能调优
使用内存来缓存数据,调用的方法是:sqlContext.cacheTable("table_name") 或 dataFrame.cache()。
此外,调用 sqlContext.uncacheTable("table_name") 可以从内存中移除数据。

内存缓存可以通过以下两个系统配置来控制。
spark.sql.inMemoryColumnarStorage.compressed。默认值为:true,表示开启压缩
spark.sql.inMemoryColumnarStorage.batchSize。缓存大小,增加它可以提升内存你利用率,但也会增加内存溢出的风险。


Catalyst执行优化器
Catalyst是Spark SQL执行优化器的代号,
所有Spark SQL语句最终都通过它来解析、优化,最终生成可以执行的Java字节码。
Catalyst是Spark SQL最核心的部分。
Catalyst是专门为Spark设计,并结合了Scala语言本身的优势,使得性能甚至超过手工编写的Scala代码。

Catalyst最主要的数据结构是树,所有SQL语句都会使用树结构来存储。
树中的每个节点有一个类,以及0或多个子节点。

 

posted @ 2021-01-27 22:23  茗::流  阅读(174)  评论(0)    收藏  举报
如有雷同,纯属参考。如有侵犯你的版权,请联系我。