实现步骤:
1)上传解压spark安装包
2)进入spark安装目录的conf目录
3)配置spark-env.sh文件
配置示例:
#本机ip地址
SPARK_LOCAL_IP=hadoop01
#spark的shuffle中间过程会产生一些临时文件,此项指定的是其存放目录,不配置默认是在
/tmp目录下
SPARK_LOCAL_DIRS=/home/software/spark/tmp
export JAVA_HOME=/home/software/jdk1.8
4)在conf目录下,编辑slaves文件
配置示例:
hadoop01
hadoop02
hadoop03
5)配置完后,将spark目录发送至其他节点,并更改对应的 SPARK_LOCAL_IP配置
启动集群
1)如果你想让01 虚拟机变为master节点,则进入01 的spark安装目录的sbin目录
执行: sh start-all.sh
2)通过jps查看各机器进程,
01:Master +Worker
02:Worker
03:Worker
3)通过浏览器访问管理界面
http://192.168.234.11:8080
4)通过spark shell 连接spark集群
进入spark的bin目录
执行:sh spark-shell.sh --master spark://192.168.234.11:7077
6)在集群中读取文件:
sc.textFile("/root/work/words.txt")
默认读取本机数据 这种方式需要在集群的每台机器上的对应位置上都一份该文件 浪费磁盘
7)所以应该通过hdfs存储数据
sc.textFile(“hdfs://hadoop01:9000/mydata/words.txt”);
注:可以在spark-env.sh 中配置选项 HADOOP_CONF_DIR 配置为hadoop的etc/hadoop的地址 使默认
访问的是hdfs的路径
注:如果修改默认地址是hdfs地址 则如果想要访问文件系统中的文件 需要指明协议为file 例如
sc.text(“file:///xxx/xx”)
集群模式运行WordCount
实现步骤
1)创建spark的项目
在scala中创建项目 导入spark相关的jar包
2)开发spark相关代码
代码示例:
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
object WordCountDriver {
def main(args: Array[String]): Unit = {
val conf=new SparkConf().setMaster(“spark://hadoop01:7077”).setAppName(“wordcount”)
val sc=new SparkContext(conf)
val data=sc.textFile(“hdfs://hadoop01:9000/words.txt”, 2)
val result=data.flatMap { x => x.split(" ") }.map { x => (x,1) }.reduceByKey(+)
result.saveAsTextFile(“hdfs://hadoop01:9000/wcresult”)
}
}
3)将写好的项目打成jar,上传到服务器,进入bin目录
执行:spark-submit --class cn.tedu.WordCountDriver /home/software/spark/conf/wc.jar
Spark架构
概述
为了更好地理解调度,我们先来鸟瞰一下集群模式下的Spark程序运行架构图。
Spark调度模块
概述
之前我们提到:Driver 的sc负责和Executor交互,完成任务的分配和调度,在底层,任务调
度模块主要包含两大部分:
1)DAGScheduler
2)TaskScheduler
它们负责将用户提交的计算任务按照DAG划分为不同的阶段并且将不同阶段的计算任务提交
到集群进行最终的计算。整个过程可以使用下图表示
RDD Objects可以理解为用户实际代码中创建的RDD,这些代码逻辑上组成了一个DAG。
DAGScheduler主要负责分析依赖关系,然后将DAG划分为不同的Stage(阶段),其中每个
Stage由可以并发执行的一组Task构成,这些Task的执行逻辑完全相同,只是作用于不同的数
据。
在DAGScheduler将这组Task划分完成后,会将这组Task提交到
TaskScheduler。TaskScheduler通过Cluster Manager申请计算资源,比如在集群中的某个
Worker Node上启动专属的Executor,并分配CPU、内存等资源。接下来,就是在Executor
中运行Task任务,如果缓存中没有计算结果,那么就需要开始计算,同时,计算的结果会回
传到Driver或者保存在本地。
Scheduler的实现概述
任务调度模块涉及的最重要的三个类是:
1)org.apache.spark.scheduler.DAGScheduler 前面提到的DAGScheduler的实现。
将一个DAG划分为一个一个的Stage阶段(每个Stage是一组Task的集合)
然后把Task Set 交给TaskScheduler模块。
2)org.apache.spark.scheduler.TaskScheduler
它的作用是为创建它的SparkContext调度任务,即从DAGScheduler接收不同Stage的任
务。向Cluster Manager 申请资源。然后Cluster Manager收到资源请求之后,在Worker为
其启动进程
3)org.apache.spark.scheduler.SchedulerBackend
是一个trait,作用是分配当前可用的资源,具体就是向当前等待分配计算资源的Task分配计
算资源(即Executor),并且在分配的Executor上启动Task,完成计算的调度过程。
4)AKKA是一个网络通信框架,类似于Netty,此框架在Spark1.8之后已全部替换成Netty
任务调度流程图
Spark Shuffle详解
概述
Shuffle,翻译成中文就是洗牌。之所以需要Shuffle,还是因为具有某种共同特征的一类数据需要最终汇聚
(aggregate)到一个计算节点上进行计算。这些数据分布在各个存储节点上并且由不同节点的计算单元处理。
以最简单的Word Count为例,其中数据保存在Node1、Node2和Node3;
经过处理后,这些数据最终会汇聚到Nodea、Nodeb处理,如下图所示。
这个数据重新打乱然后汇聚到不同节点的过程就是Shuffle。但是实际上,Shuffle过程可能会非常复杂:
1)数据量会很大,比如单位为TB或PB的数据分散到几百甚至数千、数万台机器上。
2)为了将这个数据汇聚到正确的节点,需要将这些数据放入正确的Partition,因为数据大小已经大于节点的内
存,因此这个过程中可能会发生多次硬盘续写。
3)为了节省带宽,这个数据可能需要压缩,如何在压缩率和压缩解压时间中间
做一个比较好的选择?
4)数据需要通过网络传输,因此数据的序列化和反序列化也变得相对复杂。
一般来说,每个Task处理的数据可以完全载入内存(如果不能,可以减小每个Partition的大小),因此Task可以
做到在内存中计算。但是对于Shuffle来说,如果不持久化这个中间结果,一旦数据丢失,就需要重新计算依赖的
全部RDD,因此有必要持久化这个中间结果。所以这就是为什么Shuffle过程会产生文件的原因。
如果Shuffle过程不落地,①可能会造成内存溢出 ②当某分区丢失时,会重新计算所有父分区数据
Shuffle Write
Shuffle Write,即数据是如何持久化到文件中,以使得下游的Task可以获取到其需要处理的数据的(即
Shuffle Read)。在Spark 0.8之前,Shuffle Write是持久化到缓存的,但后来发现实际应用中,shuffle过程带
来的数据通常是巨量的,所以经常会发生内存溢出的情况,所以在Spark 0.8以后,Shuffle Write会将数据持久化
到硬盘,再之后Shuffle Write不断进行演进优化,但是数据落地到本地文件系统的实现并没有改变。
1)Hash Based Shuffle Write
在Spark 1.0以前,Spark只支持Hash Based Shuffle。因为在很多运算场景中并不需要排序,因此多余的排序只
能使性能变差,比如Hadoop的Map Reduce就是这么实现的,也就是Reducer拿到的数据都是已经排好序的。
实际上Spark的实现很简单:每个Shuffle Map Task根据key的哈希值,计算出每个key需要写入的Partition然后
将数据单独写入一个文件,这个Partition实际上就对应了下游的一个Shuffle Map Task或者Result Task。因此
下游的Task在计算时会通过网络(如果该Task与上游的Shuffle Map Task运行在同一个节点上,那么此时就是一
个本地的硬盘读写)读取这个文件并进行计算。
Hash Based Shuffle Write存在的问题
由于每个Shuffle Map Task需要为每个下游的Task创建一个单独的文件,因此文件的数量就是:
number(shuffle_map_task)number(result_task)。
如果Shuffle Map Task是1000,下游的Task是500,那么理论上会产生500000个文件(对于size为0的文件
Spark有特殊的处理)。生产环境中Task的数量实际上会更多,因此这个简单的实现会带来以下问题:
1)每个节点可能会同时打开多个文件,每次打开文件都会占用一定内存。假设每个Write Handler的默认需要
100KB的内存,那么同时打开这些文件需要50GB的内存,对于一个集群来说,还是有一定的压力的。尤其是如果
Shuffle Map Task和下游的Task同时增大10倍,那么整体的内存就增长到5TB。
2)从整体的角度来看,打开多个文件对于系统来说意味着随机读,尤其是每个文件比较小但是数量非常多的情
况。而现在机械硬盘在随机读方面的性能特别差,非常容易成为性能的瓶颈。如果集群依赖的是固态硬盘,也许
情况会改善很多,但是随机写的性能肯定不如顺序写的。
2)Sort Based Shuffle Write
在Spark 1.2.0中,Spark Core的一个重要的升级就是将默认的Hash Based Shuffle换成了Sort Based Shuffle,
即spark.shuffle.manager从Hash换成了Sort,对应的实现类分别是
org.apache.spark.shuffle.hash.HashShuffleManager和
org.apache.spark.shuffle.sort.SortShuffleManager。
那么Sort Based Shuffle“取代”Hash Based Shuffle作为默认选项的原因是什么?
正如前面提到的,Hash Based Shuffle的每个Mapper都需要为每个Reducer写一个文件,供Reducer读取,即
需要产生MR个数量的文件,如果Mapper和Reducer的数量比较大,产生的文件数会非常多。
而Sort Based Shuffle的模式是:每个Shuffle Map Task不会为每个Reducer生成一个单独的文件;相反,它会
将所有的结果写到一个文件里,同时会生成一个Index文件,
Reducer可以通过这个Index文件取得它需要处理的数据。避免产生大量文件的直接收益就是节省了内存的使用和
顺序Disk IO带来的低延时。节省内存的使用可以减少GC的风险和频率。而减少文件的数量可以避免同时写多个
文件给系统带来的压力。
Sort Based Write实现详解
Shuffle Map Task会按照key相对应的Partition ID进行Sort,其中属于同一个Partition的key不会Sort。因为
对于不需要Sort的操作来说,这个Sort是负收益的;要知道之前Spark刚开始使用Hash Based的Shuffle而不是
Sort Based就是为了避免Hadoop Map Reduce对于所有计算都会Sort的性能损耗。对于那些需要Sort的运算,
比如sortByKey,这个Sort在Spark 1.2.0里还是由Reducer完成的。
①答出shuffle的定义
②spark shuffle的特点
③spark shuffle的目的
④spark shuffel的实现类,即对应优缺点
Shuffle 相关参数配置
概述
Shuffle是Spark Core比较复杂的模块,它也是非常影响性能的操作之一。因此,在这里整理
了会影响Shuffle性能的各项配置。
1)spark.shuffle.manager
Spark 1.2.0官方版本支持两种方式的Shuffle,即Hash Based Shuffle和
Sort Based Shuffle。其中在Spark 1.0之前仅支持Hash Based Shuffle。Spark 1.1引入了
Sort Based Shuffle。Spark 1.2的默认Shuffle机制从Hash变成了Sort。如果需要
Hash Based Shuffle,只需将spark.shuffle.manager设置成“hash”即可。
配置方式:
①进入spark安装目录的conf目录
②cp spark-defaults.conf.template spark-defaults.conf
③spark.shuffle.manager=hash
应用场景:当产生的临时文件不是很多时,性能可能会比sort shuffle要好。
如果对性能有比较苛刻的要求,那么就要理解这两种不同的Shuffle机制的原理,结合具体的
应用场景进行选择。
对于不需要进行排序且Shuffle产生的文件数量不是特别多时,Hash Based Shuffle可能是更
好的选择;因为Sort Based Shuffle会按照Reducer的Partition进行排序。
而Sort Based Shuffle的优势就在于可扩展性,它的出现实际上很大程度上是解决
Hash Based Shuffle的可扩展性的问题。由于Sort Based Shuffle还在不断地演进中,因此它
的性能会得到不断改善。
对于选择哪种Shuffle,如果性能要求苛刻,最好还是通过实际测试后再做决定。不过选择默
认的Sort,可以满足大部分的场景需要。
2)spark.shuffle.spill
这个参数的默认值是true,用于指定Shuffle过程中如果内存中的数据超过阈值(参考
spark.shuffle.memoryFraction的设置)时是否需要将部分数据临时写入外部存储。如果设
置为false,那么这个过程就会一直使用内存,会有内存溢出的风险。因此只有在确定内存足
够使用时,
才可以将这个选项设置为false。
3)spark.shuffle.memoryFraction
在启用spark.shuffle.spill的情况下,spark.shuffle.memoryFraction决定了当Shuffle过程中
使用的内存达到总内存多少比例的时候开始spill。在Spark 1.2.0里,这个值是0.2。
此参数可以适当调大,可以控制在0.4~0.6。
通过这个参数可以设置Shuffle过程占用内存的大小,它直接影响了写入到外部存储的频率和
垃圾回收的频率。可以适当调大此值,可以减少磁盘I/O次数。
4)spark.shuffle.blockTransferService
在Spark 1.2.0中这个配置的默认值是netty,而在之前的版本中是nio。它主要是用于在各个
Executor之间传输Shuffle数据。netty的实现更加简洁,但实际上用户不用太关心这个选项。
除非有特殊需求,否则采用默认配置即可。
5)spark.shuffle.consolidateFiles
这个配置的默认值是false。主要是为了解决在Hash Based Shuffle过程中产生过多文件的问
题。
如果配置选项为true,那么对于同一个Core上运行的Shuffle Map Task
不会产生一个新的Shuffle文件而是重用原来的。
此参数开启后,服务器有几核,就会生成几个shuffle临时文件,和MapTask和ResultTask无
关
当时官方给出建议:不太稳定,不建议使用
6)spark.shuffle.compress和spark.shuffle.spill.compress
这两个参数的默认配置都是true。spark.shuffle.compress和spark.shuffle.spill.compress都
是用来设置Shuffle过程中是否对Shuffle数据进行压缩。其中,前者针对最终写入本地文件系
统的输出文件;后者针对在处理过程需要写入到外部存储的中间数据,即针对最终的shuffle
输出文件。
Checkpoint机制
概述
checkpoint的意思就是建立检查点,类似于快照,例如在spark计算里面 计算流程DAG特别长,服务
器需要将整个DAG计算完成得出结果,但是如果在这很长的计算流程中突然中间算出的数据丢失
了,spark又会根据RDD的依赖关系从头到尾计算一遍,这样子就很费性能,当然我们可以将中间的计
算结果通过cache或者persist放到内存或者磁盘中,但是这样也不能保证数据完全不会丢失,存储的
这个内存出问题了或者磁盘坏了,也会导致spark从头再根据RDD计算一遍,所以就有了checkpoint,
其中checkpoint的作用就是将DAG中比较重要的中间数据做一个检查点将结果存储到一个高可用
的地方
代码示例:
object Driver2 {
def main(args: Array[String]): Unit = {
val conf=new SparkConf().setMaster(“local”).setAppName(“wordcount”)
val sc=new SparkContext(conf)
sc.setCheckpointDir(“hdfs://hadoop01:9000/check01”)
val data=sc.textFile(“d://data/word.txt”)
data.cache()
data.checkpoint()
val wordcount=data.flatMap {.split(" ")}.map {(,1)}.reduceByKey(+)
wordcount.cache()
wordcount.checkpoint()
wordcount.foreach{println}
}
}
总结:Spark的CheckPoint机制很重要,也很常用,尤其在机器学习中的一些迭代算法中很常
见。比如一个算法迭代10000次,如果不适用缓冲机制,如果某分区数据丢失,会导致整个计算
链重新计算,所以引入缓存机制。但是光引入缓存,也不完全可靠,比如缓存丢失或缓存存储不
下,也会导致重新计算,所以使用CheckPoint机制再做一层保证。
补充:检查目录的路径,一般都是设置到HDFS上
Spark懒执行的意义
Spark中,Transformation方法都是懒操作方法,比如map,flatMap,reduceByKey等。当触发某
个Action操作时才真正执行。
懒操作的意义:①不运行job就触发计算,避免了大量的无意义的计算,即避免了大量的无意义的
中间结果的产生,即避免产生无意义的磁盘I/O及网络传输
②更深层次的意义在于,执行运算时,看到之前的计算操作越多,执行优化的可能性就越高
Spark共享变量
概述
Spark程序的大部分操作都是RDD操作,通过传入函数给RDD操作函数来计算。这些函数在
不同的节点上并发执行,但每个内部的变量有不同的作用域,不能相互访问,所以有时会不太
方便,Spark提供了两类共享变量供编程使用——广播变量和计数器。
spark解决数据倾斜问题
文件1
id name
1 tom
2 rose
文件2
id school sno
1
s1 211
2
s2 222
3
s3 233
4
s2 244
期望得到的数据 :
1 tom s1
2 rose s2
将少量的数据转化为Map进行广播,广播会将此 Map 发送到每个节点中,如果不进行广播,
每个task执行时都会去获取该Map数据,造成了性能浪费。
完整代码
import org.apache.spark.{SparkContext, SparkConf}
import scala.collection.mutable.ArrayBuffer
object joinTest extends App{
val conf = new SparkConf().setMaster(“local[2]”).setAppName(“test”)
val sc = new SparkContext(conf)
/**
上一篇 24.大数据学习之旅——spark手把手带你入门