Spark SQL 还可以作为分布式SQL查询引擎通过JDBC或ODBC或者命令行的方式对数据库进行分布式查询。
Spark SQL 中还有一个自带的 Thrift JDBC/ODBC服务,
可以用Spark根目录下的sbin文件夹中的start-thriftserver.sh脚本启动这个服务。
$SPARK_HOME/sbin/start-thriftserver.sh
Spark中还自带了一个Beeline的命令行客户端,用户可以通过这个客户端连接启动的ThriftJDBC/ODBC,然后提交SQL。
$SPARK_HOME/bin/beeline -u jdbc:hive2://localhost:10000
Spark使用Hadoop的HDFS和YARN库,需要我们配置 Spark_Dist_ClasasPath(大写)这个变量。
vim spark-env.sh
export export SPARK_DIST_CLASSPATH=$(/opt/bigdata/hadoop/hadoop-2.8.5/bin/hadoop classpath)
或
export SPARK_DIST_CLASSPATH=$($HADOOP_HOME/bin/hadoop classpath)
以上配置Spark就可以把数据存储到Hadoop的HDFS中,也可以从HDFS中读取数据。(读写HDFS)
如果没有此配置,Spark只能读写本地文件系统的数据,无法读写HDFS数据。
export JAVA_HOME=/usr/local/java/jdk1.8
export SCALA_HOME=/usr/local/scala/scala-2.12.6export HADOOP_HOME=/opt/bigdata/hadoop/hadoop-2.8.5
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoopexport HIVE_HOME=/opt/bigdata/hive/hive-2.3.4
export HIVE_CONF_DIR=$HIVE_HOME/conf#export SPARK_HOME=/opt/bigdata/spark/spark-2.3.3
#export SPARK_CONF_DIR=$SPARK_HOME/conf
export SPARK_CONF_DIR=/opt/bigdata/spark/spark-2.3.3/confexport SPARK_DIST_CLASSPATH=$(/opt/bigdata/hadoop/hadoop-2.8.5/bin/hadoop classpath)
RDD 即弹性分布式数据集(Resilient Distributed Datasets)
RDD是Spark的核心,通过熟悉RDD编程可以看出分布式数据集在Spark多个节点分阶段(stage)并行计算的实质,
即每个节点按照Spark任务执行计划的调度有序地在每个节点分别计算结果,
最后一步步按照调度器的调度将结果合并,将结果返回到Spark客户端。
默认情况下,Spark的RDD会在你每次对它们进行Action操作是重新计算。
如果想在多个行动操作中重用同一个RDD,可以使用RDD缓存cache()让Spark把这个RDD缓存下来。
Spark把数据以多种形式持久化到许多地方(memory内存、磁盘disk)
在第一次持久化的RDD计算之后(假如缓存到内存MEMORY_ONLY),
Spark会把RDD的数据保存到内存中(以分区方式存储到集群中的各节点上),
这样在之后的行动操作中就可以重用这些数据了。
RDD默认是不进行持久化的。
在实际情况中,通常大部分的数据只使用一次。
我们可以用Spark遍历数据一遍,计算得出我们想要的结果,
所以,我们没有必要浪费存储空间来将这些数据持久化。
Spark在计算过后就默认释放掉这些使用过的数据,这种方式可以避免内存的浪费。
如果这些数据再进行一个行动操作(第2次)会重新遍历一遍原来的数据。
默认情况下,被重用的中间结果RDD可能会在每次对其进行Action操作时重新计算。
但是,可以使用cache()方法在内存中保留被重用的中间结果RDD,
这这种情况下,Spark将在集群内存上保留该RDD,以便在下次查询时进行更快的访问。
还支持在磁盘上持久化存储RDD。
RDD Lineage(又称 RDD谱系图、RDD依赖关系图)
它记录经由转换操作产生的RDD之间的依赖关系。
通过转化操作,你从已有的RDD中派生出新的RDD,
Spark会使用RDD Lineage来记录这些不同RDD之间的依赖关系。
Spark需要用这些信息来按需计算每个RDD,
也可以依靠谱系图在持久化的RDD丢失部分数据时,恢复所丢失的数据。
惰性求值
RDD的转化操作都是惰性求值的。
这意味着在被调用行动操作之前,Spark不会开始计算。
Spark会在RDD Lineage中记录所有经转化生成的RDD之前的依赖关系。
因此转化操作并不会引发真正的计算,仅仅是记录如何得到目标RDD的过程。
当RDD调用行动操作时,Spark根据RDD Lineage了解整个目标RDD计算转化链,
仅进行满足目标RDD行动操作的必要计算,从而避免没有意义的存储、计算资源的浪费,
成功地做到了按需从磁盘读取RDD相关分区数据到内存,
按需进行最小限度的必要计算,提高了Spark计算、内存资源的使用率。
把数据读取到RDD的操作也同样是惰性的。
因此,当我们调用sc.textFile()时,数据并没有读取进来,而是在必要时才会读取,
而读取时也会按照最小限度进行必要内容行读取,
如:task(10)行动操作触发Spark读取文件内容操作时,仅会读取文件前10行,而不会读取整个文件,
从而避免浪费巨大的内存资源损耗。
Spark使用惰性求值,这样就可以把一些操作合并到一起来减少计算数据的步骤。
在RDD的设计中,实现了基于RDD数据存储位置的计算本地化,减少各个节点网络间传输数据的开销。
RDD实际上是由多个分区组成的,
而一个并行计算任务对应着某个节点的一个RDD分区,
当Spark进行任务调度时,会按照"移动数据不如移动计算"的理念,
根据记录着RDD每个分区优先存储位置的列表,尽可能地将计算任务分配到其所需处理的数据块的位置,
从而实现计算本地化,避免各个节点传输数据块带来的资源浪费和计算任务拖延问题。
为了避免多次计算同一个RDD,应对数据进行持久化。
当我们让Spark持久化存储一个RDD时,
计算出RDD的节点会分别保存它们所求出的分区数据。
如果一个有持久化数据的节点发生故障,Spark刚好需要用到缓存的数据时,
如果希望节点故障的情况不会拖累我们的执行速度,也可以把数据备份到多个节点上。
可以通过在存储级别末尾加上"_2"(MEMORY_ONLY_2)来把持久化数据存为2份,以增强容错性,防止节点故障。
RDD checkpoint 容错机制
对RDD进行checkpoint操作,会将RDD直接存储到磁盘上,而不是内存,从而实现真正的数据持久化。
checkpoint实际上对RDD Lineage(依赖关系图谱)的辅助和重新切割修正。
当RDD依赖关系过于冗长和复杂时,即依赖关系已达数十代,
多个不同的分析任务同时依赖该RDD Lineage多个中间RDD时,
并且内存难以同时满足缓存多个相关中间RDD时,
可以考虑根据多个不同分析任务依赖的中间RDD的不同,
使用checkpoint将该RDD Lineage切分成多个子RDD Lineage,
这样每一个子RDD Lineage都会从各个checkpoint开始算起,
从而实现了相互独立,大大减少了由于过于冗长的 RDD Lineage造成的高昂容错成本以及内存资源不足问题。
sc.setcheckpoint("hdfs://centos02:9000/checkpointDir") //会在指定目录创建一个文件夹
rdd.checkpoint //对指定RDD设置checkpoint(rdd尚未存储到checkpoint目录,遇到Action操作才真正开始计算该RDD并存储到checkpoint目录中)
checkpoint()函数将会创建一个二进制的文件,并存储到checkpoint目录中,
在checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部移出。
对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发。
checkpoint和cache一样,是转化操作,
当遇到行动操作时,checkpoint会启动另一个任务,将数据切割拆分,保存到设置的checkpoint目录中。
1、
当使用了checkpoint后,数据被保存到HDFS,此RDD的依赖关系也会丢掉,
因为数据已经持久化到磁盘,不需要重新计算,所以会丢弃掉。
2、
强烈推荐先将数据持久化到内存中(.cache()操作),否则直接使用checkpoint会开启一个计算,浪费资源。
因为checkpoint会触发一个Job,
如果执行checkpoint的RDD是由其它RDD经过许多计算转换过来的,
如果你没有持久化这个RDD,那么又要从头开始计算该RDD,也就是做了重复的计算工作了,
所以,建议先缓存RDD然后再checkpoint
3、
对涉及大量迭代计算的重要阶段性结果设置检查点。
checkpoint会丢弃该RDD以前的依赖关系,使该RDD成为顶层父RDD,
这样在失败的时候恢复只需要恢复该RDD,而不需要重新计算该RDD了,这在迭代计算中是很有用的。
假设:
你在迭代1000次的计算中,在第999次失败了,然后你没有checkpoint,你只能重新开始恢复了,
如果恰好你在第998次迭代的时候做了一个checkpoint,那么你只需要恢复第998次产生的RDD,
然后再执行第2次迭代,完成总共1000的迭代,这样效率就很高,比较适用于迭代计算非常复杂的情况。
也就是说在恢复计算代价非常高的情况下,适当进行checkpoint会有很大的好处。
Hive metastore Parquet表转换
当向Hive metastore 中读写Parquet表时,
Spark SQL将使用Spark SQL自带的Parquet SerDe(目的是用于序列化和反序列化),
而不是Hive的SerDe,
Spark SQL自带的SerDe拥有更好的性能。
这个优化的配置参数为:spark.sql.hive.convertMetastoreParquet,默认为:开启。
元数据刷新
Spark SQL缓存了Parquet元数据以达到良好的性能。
当Hive metastore Parquet表转换为enabled时,表修改后缓存的元数据并不能刷新。
所以,当表被Hive或其他工具修改时,则必须手动刷新元数据,以保证元数据的一致性。
代码:
sparkSession.catalog.refreshTable("table_name")
在Spark上激活Hive
为了让Spark SQL 能够连接到已部署好的Hive数据仓库,
我们需要将
hive-site.xml (Hive 配置文件)
core-site.xml (Hadoop配置文件)
hdfs-site.xml (HDFS 配置文件)
这几个配置文件放到$SPARK_HOME/conf 目录下,
这样就可以通过这些配置文件找到Hive元数据库以及数据的实际存放位置了。
没有在Spark集群中部署Hive数据仓库的用户仍然可以启用Hive支持。
当hive-site.xml未配置时,
首先上下文会自动在当前目录下创建 metastore_db(Hive元数据库),
并创建由 spark.sql.warehouse.dir 来指定Hive数据仓库中数据库的默认位置.
当在Spark SQL模块下使用Hive表时,首先需要在实例化SparkSession对象时,显式指定Hive支持。
SparkSession.builder
.enableHiveSupport()
.getOrCreate();
Spark负责任务调度的是:DAGscheduler、TaskScheduler。
DAGscheduler 负责stage层面的划分和高层调度
TaskScheduler 负责同一stage内多个相同Task向Executor的分发,taskTracker负责Task的跟踪执行以及失败Task的重新执行。
Spark应用程序作为独立的进程集运行在集群上,通过主程序的SparkContext对象来协调,
SparkContext在一个驱动程序中只能有一个实例。
其实在SparkContext初始化的过程中,其内部实例化了DAGscheduler、TaskScheduler...等必要的对象,负责任务的划分、调度。
窄依赖:就是父RDD的数据只被一个子RDD使用(一对一)
宽依赖:就是父RDD的数据被多个子RDD使用 (一对多)
宽依赖,通常意味着shuffle操作,这也是stage边界的划分点。
为什么要有窄依赖和宽依赖之分呢?
因为在计算过程中假如发生了数据丢失的情况,
Spark就会通过这些依赖关系重新计算出数据丢失的那一部分。
注意数据类型的使用
能用Byte类型,不需要为了方便定义成Int类型,
一个Byte类型是8字节,Int类型是32字节。
也就是说,一旦数据进行缓存,内存的消耗将会翻倍。
写出高质量的SQL
在使用SQL查询的时候,一条高质量的SQL语句将会节省大量的查询时间,以及节省宝贵的计算资源和内存资源。
Spark调优
1、使用缓存
2、对配置属性进行调优
3、合理使用广播
4、谨慎使用带shuffle操作的方法
5、尽量在一次调用中处理一个分区的数据
6、对数据序列化
一、使用缓存
数据在内存中的计算是非常快的
我们可以把需要进行多次操作的表缓存到内存中,避免对磁盘进行多次的IO操作。
除了我们手动进行缓存之外,Spark在执行shuffle操作的时候也会自动将一些中间数据缓存,比如:reduceByKey。
二、对配置属性进行调优
spark.conf.set("key","value")
spark.sql.shuffle.partitions(200)
spark.sql.inMemoryColumnStorage.compressed(true)
spark.sql.inMemoryColumnStorage.batchSzie(1000)spark.sql.autoBroadcastJoinThreshold(10485760) 10MB
大表join小表,用户可以根据需要广播的小表调整参数的大小。
当使用连接操作的时候,会自动将小小于阈值大小的表广播给所有worder节点。
利用好这个属性可以降低数据传输的网络开销。当这个属性被设为-1时,关闭广播。
三、合理使用广播
对于比较大的变量,我们可以将它广播到每一个节点中,以节省网络通信的开销。
在不广播的情况下,每个task有一个数据的副本,
在广播之后每个Executor保留一份数据的副本。
因为广播之后减少了数据副本的数量,所以在减少网络传输开销的同时也相应地节省了一些资源。
四、谨慎使用带shuffle操作的方法
shuffle操作涉及磁盘的I/O操作、数据的序列化、网络的I/O。
某些shuffle操作会消耗大量的内存,它会把相同的key发到一个节点中,进行连接或者聚合操作。
当相同的key的数据量特别大的时候,内存有可能溢出,于是将数据写到磁盘上,发生I/O操作,导致性能急剧下降。
五、尽量在一次调用中处理一个分区的数据
mapPartitions、foreachPartitions都是在一次调用中处理一个分区的数据,
所以用mapPartitions替换map、foreachPartitions替换foreach能提高性能,
但是使用的时候需要注意内存,因为一次处理一个分区,如果分区比较大,当内存不够的时候就会出现 out of memory 异常。
六、对数据序列化
对数据进行序列化使数据更紧凑、更小,以此减少网络的传输开销,
但是会使访问对象的时间变长,因为需要对数据进行反序列化之后才能使用。
spark.serializer = org.apache.spark.serializer.KryoSerializer。
如果序列化对象太大,那么可以设置 spark.kryoserializer.buffer 来进行调整。
解决数据倾斜问题
在进行shuffle的时候会将各个节点上Key相同的数据传输到同一节点进行下一步的操作。
如果某个Key或某几个Key下的数据的数据量特别大,远远大于其它Key的数据,这时就会出现一个现象,
大部分task很快就完成结束了,剩下几个task运行得特别缓慢。
甚至有时候还会因为某个task下相同Key的数据量过大而造成内存溢出。
这就是发生了数据倾斜。
既然是数据发生了倾斜,那么主要的解决思路就是想办法让它不倾斜
1、调整分区数目
在发生数据倾斜的时候,某个task上需要处理的数据过多,我们可以调整并行度,
使原本分配给一个task的多个Key分配给多个task,这样需要task处理的Key的数目就会减少,于是task上的数据量也就减少了。
配置属性:spark.sql.shuffle.partitions,一般把它的值适当地调大。
这个方法只能缓解数据倾斜,没有从根源上解决问题。
另外需要提一点,在进行了多次操作之后会有很多小任务产生,这时可以用 coalesce来减少分区数。
并不是分区数越少越好,如果你的数据是几个特别大的并且不可分的文件,
这时每个分区中都有大量的记录,分区过少则不能充分地使用CPU的所有核心。
这种情况下就需要主动地重新分区(触发一次shuffle),增加分区的数量,以提高并行度。
2、去除多余的数据
3、将Key进行拆分,大数据化小数据
回顾一下数据倾斜的原因,单个Key或某几个Key的数据过多。
既然数据过多,我们就想办法减少单个Key的数据量。
我们可以给Key加前缀,强行让它们不同。
通过这种方式让本来应该到同一个task的数据分散到不同的task上,以此来化解数据倾斜的问题。