https://sq.163yun.com/blog/article/186627375740239872
1、Spark与MR的对比
MR中要完成数据处理,需要写入多个MR程序并且需要反复的读取磁盘中的文件,Spark中处理任务是使用多个job连续在内存中运行。
1、spark把运算的中间数据存放在内存,迭代计算效率更高;mapreduce的中间结果需要落地,需要保存到磁盘,这样必然会有磁盘io操做,影响性能。 2、spark容错性高,它通过弹性分布式数据集RDD来实现高效容错,RDD是一组分布式的存储在节点内存中的只读性质的数据集,这些集合是弹性的,某一部分丢失或者出错,可以通过整个数据集的计算流程的血缘关系来实现重建;mapreduce的话容错可能只能重新计算了,成本较高。 3、spark更加通用,spark提供了transformation和action这两大类的多个功能api,另外还有流式处理sparkstreaming模块、图计算GraphX等等;mapreduce只提供了map和reduce两种操作,流计算以及其他模块的支持比较缺乏。 4、spark框架和生态更为复杂,首先有RDD、血缘lineage、执行时的有向无环图DAG、stage划分等等,很多时候spark作业都需要根据不同业务场景的需要进行调优已达到性能要求;mapreduce框架及其生态相对较为简单,对性能的要求也相对较弱,但是运行较为稳定,适合长期后台运行。
特点:spark生态更为丰富,功能更为强大、性能更佳,适用范围更广;mapreduce更简单、稳定性好。
Spark 的运行模式有 Local(也称单节点模式),Standalone(集群模式),Spark on Yarn(运行在Yarn上),Mesos以及K8s等常用模式。
Spark-shell 参数 Spark-shell 是以一种交互式命令行方式将Spark应用程序跑在指定模式上,也可以通过Spark-submit提交指定运用程序,Spark-shell 底层调用的是Spark-submit,二者的使用参数一致的,通过- -help 查看参数:
-master: 指定运行模式,spark://host:port, mesos://host:port, yarn, or local[n]. -deploy-mode: 指定将driver端运行在client 还是在cluster. -class: 指定运行程序main方法类名,一般是应用程序的包名+类名 -name: 运用程序名称 -jars: 需要在driver端和executor端运行的jar,如mysql驱动包 -packages: maven管理的项目坐标GAV,多个以逗号分隔 -conf: 以key=value的形式传入sparkconf参数,所传入的参数必须是以spark.开头 -properties-file: 指定新的conf文件,默认使用spark-default.conf -driver-memory:指定driver端运行内存,默认1G -driver-cores:指定driver端cpu数量,默认1,仅在Standalone和Yarn的cluster模式下 -executor-memory:指定executor端的内存,默认1G -total-executor-cores:所有executor使用的cores -executor-cores: 每个executor使用的cores -driver-class-path: driver端的classpath -executor-class-path:executor端的classpath sparkconf的传入有三种方式:
1.通过在spark应用程序开发的时候用set()方法进行指定 2.通过在spark应用程序提交的时候用过以上参数指定,一般使用此种方式,因为使用较为灵活 3.通过配置spark-default.conf,spark-env.sh文件进行指定,此种方式较shell方式级别低
Local模式 Local 模式是最简单的一种Spark运行方式,它采用单节点多线程(cpu)方式运行,local模式是一种OOTB(开箱即用)的方式,只需要在spark-env.sh导出JAVA_HOME,无需其他任何配置即可使用,因而常用于开发和学习 方式:./spark-shell - -master local[n] ,n代表线程数
Standalone模式 Spark可以通过部署与Yarn的架构类似的框架来提供自己的集群模式,该集群模式的架构设计与HDFS和Yarn大相径庭,都是由一个主节点多个从节点组成,在Spark 的Standalone模式中,主,即为master;从,即为worker.
Standalone集群模式通过配置spark-env.sh和slaves文件来部署,可以通过以下配置
vi spark-env.sh
SPARK_MASTER_HOST=192.168.137.200 ##配置Master节点
SPARK_WORKER_CORES=2 ##配置应用程序允许使用的核数(默认是所有的core)
SPARK_WORKER_MEMORY=2g ##配置应用程序允许使用的内存(默认是一个G)
vi slaves
192.168.137.200
192.168.137.201
192.168.137.202
启动集群:sbin/start-all.sh
Spark on Yarn 简而言之,Spark on Yarn 模式就是将Spark应用程序跑在Yarn集群之上,通过Yarn资源调度将executor启动在container中,从而完成driver端分发给executor的各个任务。将Spark作业跑在Yarn上,首先需要启动Yarn集群,然后通过spark-shell或spark-submit的方式将作业提交到Yarn上运行。 提交作业之前需要将HADOOP_CONF_DIR或YARN_CONF_DIR配置到Spark-env.sh中:
vi spark-env.sh
HADOOP_CONF_DIR=/opt/software/hadoop-2.6.0-cdh5.7.0/etc/hadoop
on Yarn的俩种模式 Yarn的俩种模式:一种为 client;一种为 cluster,可以通过- -deploy-mode 进行指定,也可以直接在 - -master 后面使用 yarn-client和yarn-cluster进行指定 俩种模式的区别:在于driver端启动在本地(client),还是在Yarn集群内部的AM中(cluster)
3、RDD的五大特性
(1)RDD是一个由多个partition(某个节点里的某一片连续的数据)组成的的List;将数据加载为RDD时,一般一个hdfs里的block会加载为一个partition。
(2)RDD的每个partition上面都会有function,也就是函数应用,其作用是实现RDD之间partition的转换。
(3)依赖于其他RDD的列表,RDD会记录它的依赖 ,为了容错,也就是说在内存中的RDD操作时出错或丢失会进行重算。
(4)如果RDD里面存的数据是key-value形式,则可以传递一个自定义的Partitioner进行重新分区,例如这里自定义的Partitioner是基于key进行分区,那则会将不同RDD里面的相同key的数据放到同一个partition里面。
(5)RDD每个分区都有一个优先位置列表。它会存储每个Partition的优先位置,对于一个HDFS文件来说,就是每个Partition块的位置。
4、transformation类算子
https://blog.csdn.net/qq_41955099/article/details/93802602
map(func) 通过应用一个函数的所有元素,返回一个新的分布式数据集。 filter(func) 通过选择函数返回true的那些元素来形成,返回一个新的数据集。 flatMap(func) 与map类似,但每个输入项都可以映射到0个或多个输出项(因此函数应该返回一个序列而不是单个项)。 mapPartitions(func) 与map类似,但运行在RDD的每个分区(块),所以函数必须是迭代器Iterator => Iterator,当运行在一个类型T的RDD。 mapPartitionsWithIndex(func) 与mapPartitions类似,但也为函数提供一个表示分区索引的整数值,所以函数必须是(Int, Iterator) => Iterator类型当运行在一个类型T的RDD。 sample(withReplacement, fraction, seed) 使用给定随机数字生成器的种子,对数据的一小部分进行采样,无论有无替换。 union(otherDataset) 返回一个新的数据集,其中包含源数据集和参数中的元素的联合。 intersection(otherDataset) 返回一个新的RDD,它包含源数据集和参数中的元素的交集。 distinct([numTasks])) 返回一个包含源数据集的不同元素的新数据集。 groupByKey([numTasks]) 当调用一个(K, V)对的数据集时,返回一个(K, 可迭代的)对的数据集。注意:如果要对每个键执行聚合(如汇总或平均值),使用简化的方法或聚合键将会获得更好的性能。注意:在默认情况下,输出的并行度取决于父RDD分区的数量,你可以通过一个可选的numTasks参数来设置不同数量的任务。 reduceByKey(func, [numTasks]) 当调用(K, V)的数据集对,返回一个数据集(K, V)对每个键的值在哪里聚合使用给定减少函数func,必须(V, V) => V形似groupByKey,减少任务的数量通过一个可选的第二个参数是可配置的。 aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) 当调用一个(K, V)对的数据集时,返回一个(K, U)对的数据集,其中每个键的值使用给定的组合函数和一个中立的“零”值进行聚合。允许一个与输入值类型不同的聚合值类型,同时避免不必要的分配。与groupByKey一样,reduce任务的数量是通过可选的第二个参数进行配置的。 sortByKey([ascending], [numTasks]) 当调用一个(K, V)对K实现排序的数据集时,返回一个由按升序或降序排列的(K, V)对的数据集,如布尔提升参数中所指定的那样。 join(otherDataset, [numTasks]) 当调用类型的数据集(K,V)和(K,W)时,返回一个数据集(K,(V,W))对每一个键的所有对元素。外部连接通过左外连接、右外连接与全外连接。 cogroup(otherDataset, [numTasks]) 当调用类型的数据集(K,V)和(K,W)时,返回一个 (K, (Iterable, Iterable)) 元素的数据集。这个操作也称为group分组。 cartesian(otherDataset) 当调用类型T和U的数据集时,返回一个(T,U)对(所有对元素)的数据集。 pipe(command, [envVars]) 通过shell命令对RDD的每个分区进行管道,例如Perl或bash脚本。RDD元素被写入到进程的stdin中,输出到其stdout的输出被作为字符串的RDD返回。 coalesce(numPartitions) 将RDD中的分区数量减少到num分区。在过滤大数据集之后,可以更有效地运行操作。 repartition(numPartitions) 对RDD中的数据进行随机重组,以创建多个或更少的分区,并在它们之间进行平衡。这通常会使网络上的所有数据都被打乱。 repartitionAndSortWithinPartitions(partitioner) 根据给定的分区重新分区RDD,在每个结果分区中,根据它们的键对记录进行排序。这比调用重新分区更有效,然后在每个分区中进行排序,因为它可以将排序推入到洗牌机器中。
5、action类算子
reduce(func) 使用函数func聚合数据集的元素(它需要两个参数并返回一个参数)。这个函数应该是可交换的和结合的,这样它就可以在并行计算中得到正确的计算。 collect() 将数据集的所有元素作为驱动程序的数组返回。这通常是在过滤器或其他操作之后才会有用的,这些操作返回一个足够小的数据子集。 count() 返回数据集中的元素数量。 first() 返回数据集的第一个元素(类似于take(1))。 take(n) 返回一个带有数据集的第n个元素的数组。 takeSample(withReplacement, num, [seed]) 返回一个包含数据集的num元素随机样本的数组,不管有没有替换,都可以选择预先指定一个随机数生成器种子。 takeOrdered(n, [ordering]) 返回RDD的前n个元素,使用它们的自然顺序或自定义比较器。 saveAsTextFile(path) 在本地文件系统、HDFS或任何其他hadoop支持的文件系统中,将数据集的元素作为文本文件(或一组文本文件)写入一个给定目录中。Spark将调用每个元素的toString,将其转换为文件中的一行文本。 saveAsSequenceFile(path) (Java and Scala) 在本地文件系统、HDFS或任何其他Hadoop支持的文件系统中,将数据集的元素作为Hadoop序列文件写入给定路径。这可用于实现Hadoop可写接口的键-值对的RDDs。在Scala中,它还可以在可隐式可转换的类型中使用(Spark包含诸如Int、Double、String等基本类型的转换)。 saveAsObjectFile(path) (Java and Scala) 使用Java序列化以简单的格式编写数据集的元素,然后可以使用sparkcontext.objectfile()装载数据。 countByKey() 只有在类型的rdd(K,V)上才可用。返回一个hashmap(K,Int)对每个键的计数。 foreach(func) 在数据集的每个元素上运行一个函数func。这通常是为了一些副作用,比如更新一个累加器或者与外部存储系统进行交互。注意:除了foreach()之外的累计变量之外,修改变量可能导致未定义的行为。
6、统计每一个单词出现的次数 WordCount
思路:
-
textFile:读取文件
-
flatMap:将每行单词按空格分开
-
mapToPair:把每个单词后面加上一个数量组成
-
reduceByKey:将相同的单词后面数量相加
-
foreach:打印
或者:arr.flatMap(.split(" ")).map((,1)).groupBy(.1).map(t=>(t.1,t.2.size))
或:arr.flatMap(.split(" ")).map((,1)).groupBy(.1).mapValues(.foldLeft(0)(+.2))
7、持久化类算子的原理以及使用方式
https://www.cnblogs.com/Transkai/p/11347224.html
持久化RDD:只要调用其cache方法或者persist方法即可。在该RDD第一次被计算出来时,就会直接缓存在每一个节点。而且spark持久化机制还是自动容错的。如果持久化的RDD的任何partion丢失了,那么soark会自动通过其源RDD使用transformation 操作重新计算该partion。
Spark非常重要的一个功能特性就是可以将RDD持久化在内存中。当对RDD执行持久化操作时,每个节点都会将自己操作的RDD的partition持久化到内存中,并且在之后对该RDD的反复使用中,直接使用内存缓存的partition。这样的话,对于针对一个RDD反复执行多个操作的场景,就只要对RDD计算一次即可,后面直接使用该RDD,而不需要反复计算多次该RDD。
巧妙使用RDD持久化,甚至在某些场景下,可以将spark应用程序的性能提升10倍。对于迭代式算法和快速交互式应用来说,RDD持久化,是非常重要的。
要持久化一个RDD,只要调用其cache()或者persist()方法即可。在该RDD第一次被计算出来时,就会直接缓存在每个节点中。而且Spark的持久化机制还是自动容错的,如果持久化的RDD的任何partition丢失了,那么Spark会自动通过其源RDD,使用transformation操作重新计算该partition。
cache()和persist()的区别在于,cache()是persist()的一种简化方式,cache()的底层就是调用的persist()的无参版本,同时就是调用persist(MEMORY_ONLY),将数据持久化到内存中。如果需要从内存中清楚缓存,那么可以使用unpersist()方法。
Spark自己也会在shuffle操作时,进行数据的持久化,比如写入磁盘,主要是为了在节点失败时,避免需要重新计算整个过程。
广播变量:
Spark提供的Broadcast Variable,是只读的。并且在每个节点上只会有一份副本,而不会为每个task都拷贝一份副本。因此其最大作用,就是减少变量到各个节点的网络传输消耗,以及在各个节点上的内存消耗。此外,spark自己内部也使用了高效的广播算法来减少网络消耗。
可以通过调用SparkContext的broadcast()方法,来针对某个变量创建广播变量。然后在算子的函数内,使用到广播变量时,每个节点只会拷贝一份副本了。每个节点可以使用广播变量的value()方法获取值。记住,广播变量,是只读的。
例:
val factor = 3 val factorBroadcast = sc.broadcast(factor)
val arr = Array(1, 2, 3, 4, 5) val rdd = sc.parallelize(arr) val multipleRdd = rdd.map(num => num * factorBroadcast.value())
multipleRdd.foreach(num => println(num))
累加器:
Spark提供的Accumulator,主要用于多个节点对一个变量进行共享性的操作。Accumulator只提供了累加的功能。但是确给我们提供了多个task对一个变量并行操作的功能。但是task只能对Accumulator进行累加操作,不能读取它的值。只有Driver程序可以读取Accumulator的值。
例:
val sumAccumulator = sc.accumulator(0)
val arr = Array(1, 2, 3, 4, 5) val rdd = sc.parallelize(arr) rdd.foreach(num => sumAccumulator += num)
println(sumAccumulator.value)
8、Spark集群的架构,Master Wokrer的作用
master节点常驻master守护进程,负责管理worker节点,我们从master节点提交应用。
worker节点常驻worker守护进程,与master节点通信,并且管理executor进程。
master启动后会连zookeeper,因为在spark-env.sh中配置了SPARK_DAEMON_JAVA_OPTS。Worker也会连zookeeper,然后通过zookeeper知道master的地址。所以说Worker是间接地通过zookeeper知道master的位置的。
9、集群搭建
https://blog.csdn.net/qq_42825815/article/details/84071702
10、client cluster两种提交任务的方式的区别以及应用场景
https://blog.csdn.net/m0_37758017/article/details/80469263
Client模式:
Driver进程会在当前客户端启动,客户端进程一直存在直到应用程序运行结束
工作流程如下: 1.启动master和worker . worker负责整个集群的资源管理,worker负责监控自己的cpu,内存信息并定时向master汇报 2.在client中启动Driver进程,并向master注册 3.master通过rpc与worker进行通信,通知worker启动一个或多个executor进程 4.executor进程向Driver注册,告知Driver自身的信息,包括所在节点的host等 5.Driver对job进行划分stage,并对stage进行更进一步的划分,将一条pipeline中的所有操作封装成一个task,并发送到向自己注册的executor 进程中的task线程中执行 6.应用程序执行完成,Driver进程退出
cluster模式:
Driver进程将会在集群中的一个worker中启动,而且客户端进程在完成自己提交任务的职责后,就可以退出,而不用等到应用程序执行完毕
工作流程如下: 1.在集群的节点中,启动master , worker进程,worker进程启动成功后,会向Master进行注册。 2.客户端提交任务后,ActorSelection(master的actor引用),然后通过ActorSelection给Master发送注册Driver请求(RequestSubmitDriver) 3.客户端提交任务后,master通知worker节点启动driver进程。(worker的选择是随意的,只要worker有足够的资源即可) driver进程启动成功后,将向Master返回注册成功信息 4.master通知worker启动executor进程 5.启动成功后的executor进程向driver进行注册 6.Driver对job进行划分stage,并对stage进行更进一步的划分,将一条pipeline中的所有操作封装成一个task,并发送到向自己注册的executor 进程中的task线程中执行 7.所有task执行完毕后,程序结束
Mater负责整个集群的资源的管理和创建worker,worker负责当前结点的资源的管理,并会将当前的cpu,内存等信息定时告知master,并且负责创建Executor进程(也就是最小额资源分配单位),Driver负责整个应用任务的job的划分和stage的切割以及task的切割和优化,并负责把task分发到worker对应的节点的executor进程中的task线程中执行, 并获取task的执行结果,Driver通过SparkContext对象与spark集群获取联系,得到master主机host,就可以通过rpc向master注册自己。
11、提交命令 各个选项的作用
https://www.cnblogs.com/weiweifeng/p/8073553.html
12、Spark资源调度原理
每个spark作业都会运行自己独立的一批executor进程,此时集群管理器会为我们提供同时调度多个作业的功能。 第二,在每个spark作业内部,多个job也可以并行执行,比如说spark-shell就是一个spark application,但是随着我们输入scala rdd action类代码,就会触发多个job,多个job是可以并行执行的。 为这种情况,spark也提供了不同的调度器来在一个application内部调度多个job。
静态资源分配
当一个spark application运行在集群中时,会获取一批独立的executor进程专门为自己服务,比如运行task和存储数据。如果多个用户同时在使用一个集群,并且同时提交多个作业,那么根据cluster manager的不同,有几种不同的方式来管理作业间的资源分配。
最简单的一种方式,所有cluster manager都提供的,也就是静态资源分配。在这种方式下,每个作业都会被给予一个它能使用的最大资源量的限额,并且可以在运行期间持有这些资源。这是spark standalone集群和YARN集群使用的默认方式。
Standalone集群: 默认情况下,提交到standalone集群上的多个作业,会通过FIFO的方式来运行,每个作业都会尝试获取所有的资源。可以限制每个作业能够使用的cpu core的最大数量(spark.cores.max),或者设置每个作业的默认cpu core使用量(spark.deploy.defaultCores)。最后,除了控制cpu core之外,每个作业的spark.executor.memory也用来控制它的最大内存的使用。
YARN: --num-executors属性用来配置作业可以在集群中分配到多少个executor,--executor-memory和--executor-cores可以控制每个executor能够使用的资源。
要注意的是,没有一种cluster manager可以提供多个作业间的内存共享功能。如果你想要通过这种方式来在多个作业间共享数据,建议就运行一个spark作业,但是可以接收网络请求,并对相同RDD的进行计算操作。在未来的版本中,内存存储系统,比如Tachyon会提供其他的方式来共享RDD数据。
13、Spark任务调度
任务调度过程:
1、启动集群后,Worker节点会向Master节点汇报资源情况,Master掌握了集群资源情况。
2、当Spark提交一个Application后,根据RDD之间的依赖关系将Application形成一个DAG有向无环图。任务提交后,Spark会在Driver端创建两个对象:DAGScheduler和TaskScheduler。
3、DAGScheduler是任务调度的高层调度器,是一个对象。DAGScheduler的主要作用就是将DAG根据RDD之间的宽窄依赖关系划分为一个个的Stage,然后将这些Stage以TaskSet的形式提交给TaskScheduler(TaskScheduler是任务调度的低层调度器,这里TaskSet其实就是一个集合,里面封装的就是一个个的task任务,也就是stage中的并行度task任务)
4、TaskSchedule会遍历TaskSet集合,拿到每个task后会将task发送到计算节点Executor中去执行(其实就是发送到Executor中的线程池ThreadPool去执行)。
5、task在Executor线程池中的运行情况会向TaskScheduler反馈,
6、当task执行失败时,则由TaskScheduler负责重试,将task重新发送给Executor去执行,默认重试3次。如果重试3次依然失败,那么这个task所在的stage就失败了。
7、stage失败了则由DAGScheduler来负责重试,重新发送TaskSet到TaskSchdeuler,Stage默认重试4次。如果重试4次以后依然失败,那么这个job就失败了。job失败了,Application就失败了。
8、TaskScheduler不仅能重试失败的task,还会重试straggling(落后,缓慢)task(也就是执行速度比其他task慢太多的task)。如果有运行缓慢的task那么TaskScheduler会启动一个新的task来与这个运行缓慢的task执行相同的处理逻辑。两个task哪个先执行完,就以哪个task的执行结果为准。这就是Spark的推测执行机制。在Spark中推测执行默认是关闭的。推测执行可以通过spark.speculation属性来配置。
RDD宽窄依赖
-
窄依赖
-
从 RDD 的 parition 角度来看
-
父 RRD 的 parition 和 子 RDD 的 parition 之间的关系是一对一的 (或者是多对一的)。
-
不会有 shuffle 产生
-
-
宽依赖
-
父 RRD 的 parition 和 子 RDD 的 parition 之间的关系是一对多的
-
会产生shuffle
-
14、Spark中两种最重要shuffle
https://www.cnblogs.com/itboys/p/9226479.html
sortShuffle(Spark1.6+) hashShuffle(Spark1.6-)
mapreduce的shuffle原理:
map task端操作
每个map task都有一个内存缓冲区(默认是100MB),存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task结束后再对磁盘中这个map task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待reduce task来拉数据。
Spill过程:这个从内存往磁盘写数据的过程被称为Spill,中文可译为溢写。整个缓冲区有个溢写的比例spill.percent(默认是0.8),当达到阀值时map task 可以继续往剩余的memory写,同时溢写线程锁定已用memory,先对key(序列化的字节)做排序,如果client程序设置了Combiner,那么在溢写的过程中就会进行局部聚合。
Merge过程:每次溢写都会生成一个临时文件,在map task真正完成时会将这些文件归并成一个文件,这个过程叫做Merge。
reduce task端操作
当某台TaskTracker上的所有map task执行完成,对应节点的reduce task开始启动,简单地说,此阶段就是不断地拉取(Fetcher)每个map task所在节点的最终结果,然后不断地做merge形成reduce task的输入文件。
Copy过程:Reduce进程启动一些数据copy线程(Fetcher)通过HTTP协议拉取TaskTracker的map阶段输出文件
Merge过程:Copy过来的数据会先放入内存缓冲区(基于JVM的heap size设置),如果内存缓冲区不足也会发生map task的spill(sort 默认,combine 可选),多个溢写文件时会发生map task的merge
下面总结下mapreduce的关键词:
存储相关的有:内存缓冲区,默认大小,溢写阀值
主要过程:溢写(spill),排序,合并(combine),归并(Merge),Copy或Fetch
相关参数:内存缓冲区默认大小,JVM heap size,spill.percent
关于排序方法:
在Map阶段,k-v溢写时,采用的正是快排;而溢出文件的合并使用的则是归并;在Reduce阶段,通过shuffle从Map获取的文件进行合并的时候采用的也是归并;最后阶段则使用了堆排作最后的合并过程。
SortShuffleManager
SortShuffleManager运行原理 SortShuffleManager的运行机制主要分成两种,一种是普通运行机制,另一种是bypass运行机制。当shuffle read task的数量小于等于spark.shuffle.sort.bypassMergeThreshold参数的值时(默认为200),就会启用bypass机制。
普通运行机制
下图说明了普通的SortShuffleManager的原理。在该模式下,数据会先写入一个内存数据结构中,此时根据不同的shuffle算子,可能选用不同的数据结构。如果是reduceByKey这种聚合类的shuffle算子,那么会选用Map数据结构,一边通过Map进行聚合,一边写入内存;如果是join这种普通的shuffle算子,那么会选用Array数据结构,直接写入内存。接着,每写一条数据进入内存数据结构之后,就会判断一下,是否达到了某个临界阈值。如果达到临界阈值的话,那么就会尝试将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构。
在溢写到磁盘文件之前,会先根据key对内存数据结构中已有的数据进行排序。排序过后,会分批将数据写入磁盘文件。默认的batch数量是10000条,也就是说,排序好的数据,会以每批1万条数据的形式分批写入磁盘文件。写入磁盘文件是通过Java的BufferedOutputStream实现的。BufferedOutputStream是Java的缓冲输出流,首先会将数据缓冲在内存中,当内存缓冲满溢之后再一次写入磁盘文件中,这样可以减少磁盘IO次数,提升性能。
一个task将所有数据写入内存数据结构的过程中,会发生多次磁盘溢写操作,也就会产生多个临时文件。最后会将之前所有的临时磁盘文件都进行合并,这就是merge过程,此时会将之前所有临时磁盘文件中的数据读取出来,然后依次写入最终的磁盘文件之中。此外,由于一个task就只对应一个磁盘文件,也就意味着该task为下游stage的task准备的数据都在这一个文件中,因此还会单独写一份索引文件,其中标识了下游各个task的数据在文件中的start offset与end offset。
SortShuffleManager由于有一个磁盘文件merge的过程,因此大大减少了文件数量。比如第一个stage有50个task,总共有10个Executor,每个Executor执行5个task,而第二个stage有100个task。由于每个task最终只有一个磁盘文件,因此此时每个Executor上只有5个磁盘文件,所有Executor只有50个磁盘文件。
bypass运行机制
下图说明了bypass SortShuffleManager的原理。bypass运行机制的触发条件如下:
shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值(默认为200)。 不是排序类的shuffle算子(比如reduceByKey)。 此时task会为每个下游task都创建一个临时磁盘文件,并将数据按key进行hash然后根据key的hash值,将key写入对应的磁盘文件之中。当然,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后再溢写到磁盘文件的。最后,同样会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。
该过程的磁盘写机制其实跟未经优化的HashShuffleManager是一模一样的,因为都要创建数量惊人的磁盘文件,只是在最后会做一个磁盘文件的合并而已。因此少量的最终磁盘文件,也让该机制相对未经优化的HashShuffleManager来说,shuffle read的性能会更好。
而该机制与普通SortShuffleManager运行机制的不同在于:第一,磁盘写机制不同;第二,不会进行排序。也就是说,启用该机制的最大好处在于,shuffle write过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销。
shuffle调优
https://blog.csdn.net/jh_zhai/article/details/81714212
https://blog.csdn.net/qichangjian/article/details/88039576
15、Spark高可用集群的搭建
https://blog.csdn.net/Li_peipei/article/details/86498231
Spark WEBUI详解:
https://www.jianshu.com/p/8143a50a5af9
16、SparkSQL简介
https://blog.csdn.net/android_xue/article/details/80156682
Spark SQL是Apache Spark用于处理结构化数据的模块。
特征:
1、集成
将SQL查询与Spark程序无缝对接。Spark SQL允许您使用SQL或熟悉的DataFrame API查询Spark程序内的结构化数据。
2、统一的数据访问
DataFrames和SQL提供了访问各种数据源的常用方式,包括Hive,Avro,Parquet,ORC,JSON和JDBC。 您甚至可以通过这些来源加入数据。
3、Hive集成
在现有仓库上运行SQL或HiveQL查询。
Spark SQL支持HiveQL语法以及Hive SerDes和UDF,允许您访问现有的Hive仓库。
4、标准连接
通过JDBC或ODBC连接。服务器模式为商业智能工具提供行业标准的JDBC和ODBC连接。
SparkSQL中的DataFrame与RDD的区别:
https://www.cnblogs.com/guoyu1/p/12076355.html
spark:
Spark为结构化数据处理引入了一个称为Spark SQL的编程模块。它提供了一个称为DataFrame的编程抽象,并且可以充当分布式SQL查询引擎。
Spark SQL的特性
以下是Spark SQL的功能
集成
无缝地将SQL查询与Spark程序混合。 Spark SQL允许您将结构化数据作为Spark中的分布式数据集(RDD)进行查询,在Python,Scala和Java中集成了API。这种紧密的集成使得可以轻松地运行SQL查询以及复杂的分析算法。
统一数据访问
加载和查询来自各种来源的数据。 Schema-RDDs提供了一个有效处理结构化数据的单一接口,包括Apache Hive表,镶木地板文件和JSON文件。
Hive兼容性
在现有仓库上运行未修改的Hive查询。 Spark SQL重用了Hive前端和MetaStore,为您提供与现有Hive数据,查询和UDF的完全兼容性。只需将其与Hive一起安装即可。
标准连接
通过JDBC或ODBC连接。 Spark SQL包括具有行业标准JDBC和ODBC连接的服务器模式。
可扩展性
对于交互式查询和长查询使用相同的引擎。 Spark SQL利用RDD模型来支持中查询容错,使其能够扩展到大型作业。不要担心为历史数据使用不同的引擎。
Spark SQL架构
下图说明了Spark SQL的体系结构
此架构包含三个层,即Language API,Schema RDD和数据源。
语言API
Spark与不同的语言和Spark SQL兼容。 它也是由这些语言支持的API(python,scala,java,HiveQL)。
模式RDD
Spark Core是使用称为RDD的特殊数据结构设计的。 通常,Spark SQL适用于模式,表和记录。 因此,我们可以使用Schema RDD作为临时表。 我们可以将此Schema RDD称为数据帧。
数据源
通常spark-core的数据源是文本文件,Avro文件等。但是,Spark SQL的数据源不同。 这些是Parquet文件,JSON文档,HIVE表和Cassandra数据库。
17、SparkSQL实战
Spark读取parquet格式的文件:
val userDF = spark.read.parquet("file:///usr/local/Cellar/spark-2.3.0/examples/src/main/resources/users.parquet")
把dataframe 转成 parquet 文件:
val jsonPeopleDF = spark.read.json("/usr/local/Cellar/spark-2.3.0/examples/src/main/resources/people.json") jsonPeopleDF.write.parquet("/Users/walle/Documents/D3/d1.parquet") val d1DF = spark.read.parquet("file:///Users/walle/Documents/D3/d1.parquet") d1DF.show
SparkSQL操作:
https://www.cnblogs.com/lijinze-tsinghua/p/8505281.html
SparkSQL的读取和保存:
https://www.jianshu.com/p/27748c3d7e40
Spark的UDF和UDAF函数:
https://blog.csdn.net/liangzelei/article/details/80608302
Spark的开窗函数:
https://blog.csdn.net/weixin_39966065/article/details/93099293
18、SparkStreaming介绍