大数据Spark面试题

4 Spark

4.1 Hadoop和Spark的相同点与不同点

Hadoop底层使用MapReduce计算架构,只有Map和Reduce两种操作,表达能力比较欠缺,而且在map、reduce计算过程会重复的读写HDFS,造成大量的磁盘IO读写操作,所以适合高时延环境下批处理计算的应用;

Spark是基于内存的分布式计算架构,提供更加丰富的数据集操作类型,主要分成转换操作和行动操作,包括map、reduce、filter、groupbykey、reducebykey等操作,数据分析更加快速,所以适合低时延环境下计算的应用;

Spark和Hadoop的区别在于迭代式计算模型。基于MapReduce框架的Hadoop主要分为Map和Reduce两个阶段,所以在一个job里面能做的处理很有限。Spark计算模型是基于内存的迭代式计算模型,可以分为n个阶段,根据用户编写的RDD算子和程序,在处理完一个阶段后可以继续往下处理很多个阶段,而不只是两个阶段。所以Spark相对于MapReduce,计算模型更加灵活,可以提供更加强大的功能;

但是Spark也有劣势,由于Spark是基于内存计算的,在实际面对大数据应用场景中,在没有进行调优的情况下,可能会出现OOM内存溢出的情况,导致Spark程序可能无法运行起来;

4.2 Spark的工作机制

用户首先在客户端(client)提交作业后,会由Driver端运行main方法并创建SparkContext上下文对象。然后开始执行RDD算子,形成Dag有向无环图输入到GAGScheduler对象中,并按照RDD之间的宽依赖关系划分stage,每一个stage对应一个TaskSet,并把TaskSet发送给TaskScheduler对象,TaskScheduler对象依次遍历每个TaskSet集合,取出每一个Task,然后将每一个Task提交到Worker节点上的Executor进程中进行执行;

4.3 Spark任务的提交、划分、调度流程(Standalone模式)

(1)在Driver端,脚本启动,执行spark-submit提交jar开始运行程序;
(2)客户端向资源管理器Master发送注册和申请资源的请求;
(3)Master接收到客户端的请求之后,然后向指定的Worker发送请求,Worker接收到请求之后,就开启对应的executor进程;
(4)Executor进程会向Driver端发送注册请求,然后申请要计算的task;
(5)TaskScheduler提交task到executor进程中运行;
(6)当所有的task任务都运行完成之后,Driver端向Master发送注销请求;
(7)Master接收到Driver端的注销请求之后,然后通知对应的Worker节点关闭executor进程,最后Worker节点上的计算资源得到释放;

Driver端的流程:
(1)这时候,Driver端会运行客户端程序中的main()方法;
(2)然后在main()方法中,构建SparkContext对象,在构建SparkContext对象的内部,也同时构建了GAGScheduler和TaskScheduler对象;
(3)接着根据程序中涉及到的RDD操作,按照RDD与RDD之间的依赖关系,生成一个DAG有向无环图,将DAG有向无环图发送给GAGScheduler对象;
(4)GAGScheduler对象获得DAG有向无环图之后,按照宽依赖,进行Stage划分,一个Stage内部有很多可以并行运行的Task线程,并把这些可以并行运行的Task线程封装到一个TaskSet集合中,然后再把一个个TaskSet集合发送给TaskScheduler对象;
(5)TaskScheduler对象获得TaskSet集合之后,然后按照Stage与Stage之间的依赖关系,前面Stage中的Task先运行,后面Stage中的Task再运行,最后TaskScheduler对象依次遍历每个TaskSet集合,取出每一个Task,将每一个Task提交到Worker节点上的Executor进程中执行;

4.4 Spark任务的提交、划分、调度流程(Yarn模式)

(1)spark-submit通过命令行的方式提交任务;
(2)任务提交后会和ResourceManager进行通讯,并申请启动ApplicationMaster;
(3)随后ResourceManager进行分配container容器,并在合适的NodeManager上启动ApplicationMaster;
(4)ApplicationMaster启动后会创建Driver线程来执行用户的作业;
(5)接着,ApplicationMaster继续向ResourceManager申请资源并创建Executor进程;
(6)ResourceManager接到资源申请后,会分配container容器,并在合适的NodeManager上启动Executor进程;
(7)Executor启动之后会向Driver进行反向注册;
(8)在Executor全部注册完之后,Driver端开始执行main函数,之后在每执行到Action算子时,会触发一个job,并根据宽依赖开始划分stage,然后每个stage生成对应的TaskSet,然后遍历TaskSet,将每个task分发到各个Executor上进行执行;

4.5 Spark on Yarn 模式有哪些优点?

(1)相较于Spark自带的Standalone模式,Yarn的资源分配更加细致;
(2)Application部署简化,例如Spark的应用程序由客户端提交后,由Yarn负责资源的管理和调度,利用Container作为资源隔离的单位,以它为单位去使用内存CPU等;
(3)Yarn通过队列的方式管理同时运行在Yarn集群中的多个服务,可根据不同类型的应用程序负载情况,调整对应的资源使用量,实现资源弹性管理;

4.6 yarn-client与yarn-cluster的区别?

yarn-client一般用于测试环境调试程序;yarn-cluster是用于生产环境;
(1)yarn-client:spark-submit在提交的时候发送给ResourceManager,请求启动ApplicationMaster,分配一个container,在某个NodeManager上,启动ApplicationMaster,但是这里的ApplicationMaster只是一个ExecutorLauncher,功能是很有限的。ApplicationMaster启动后会找ResourceManager申请container,启动executor,ApplicationMaster链接其他的NodeManager,用container的资源来启动executor。executor会反向注册到本地的driver上;
(2)yarn-cluster:spark-submit在提交的时候请求到ResourceManager,请求来启动ApplicationMaster,ResourceManager接收到请求后会分配一个container,在某个NodeManager上启动ApplicationMaster,ApplicationMaster启动后会反过来向ResourceManager进行通讯,其实ApplicationMaster就相当于是driver,ApplicationMaster向ResourceManager请求container,启动excutor,然后ResourceManager会分配一批container,用于启动executor,ApplicationMaster链接其他的NodeManager,来启动executor,这里的NodeManager相当于worker,executor向ApplicationMaster反向注册。ResourceManager相当于之前的master;

总结:
yarn-client主要用于测试,因为driver运行在本地客户端,负责调度Application,会与yarn集群产生大量的网络通信,好处是执行时,本地可以看到所有的log,方便调试;

yarn-cluster用于生产环境,driver运行在nodemanager,没有网卡流量激增的问题,缺点是不方便调试,只能同过yarn application -log applacation_id查看;

4.7 RDD持久化原理

Spark非常重要的一个功能特性就是可以将RDD持久化在内存中。
调用cache()和persist()方法即可。
cache()和persist()方法的区别在于:cache()是persist()的一种简化方式,cache()底层调用的是persist()的参数版本。persist(StorageLevel.MEMORY_ONLY),将数据持久化到内存中;

如果需要从内存中清除缓存,可以使用 unpersist()方法。RDD 持久化是可以手动选择不同的策略的。在调用 persist()时传入对应的StorageLevel即可;

StorageLevel的等级(10种):
在这里插入图片描述

4.8 Checkpoint()原理

应用场景:当 spark 应用程序特别复杂,从初始的RDD开始到最后整个应用程序完成有很多的步骤,而且整个应用运行时间特别长,这种情况下就比较适合使用checkpoint功能。

原因:对于特别复杂的Spark应用,会出现某个反复使用的RDD,即使之前持久化过但由于节点的故障导致数据丢失了,没有容错机制,所以需要重新计算一次数据。

Checkpoint首先会调用SparkContext的setCheckPointDIR()方法,设置一个容错的文件系统的目录,比如说HDFS;然后对RDD调用checkpoint()方法。之后在RDD所处的job运行结束之后,会启动一个单独的job,来将checkpoint过的RDD数据写入之前设置的文件系统,进行高可用、容错的类持久化操作。

4.9 Checkpoint和持久化机制的区别

最主要的区别在于持久化只是将数据保存在BlockManager中,但是rdd的lineage(血缘关系,依赖关系)是不变的。但是checkpoint执行完之后,rdd已经没有之前所谓的依赖rdd了,而只有一个rdd为checkpointRDD,checkpoint之后rdd 的lineage就改变了。

持久化的数据丢失的可能性更大,因为节点的故障会导致磁盘、内存的数据丢失。但是 checkpoint 的数据通常是保存在高可用的文件系统中,比如HDFS中,所以数据丢失可能性比较低;

4.10 什么是RDD

RDD是Spark提供的核心抽象,全称为弹性分布式数据集,可以简单理解成一个数据结构。在Spark中所有的算子都是基于RDD来执行的,RDD执行过程中会形成Dag图,然后形成lineage保证容错性等。

RDD在逻辑上是一个HDFS文件,在抽象上是一种元素集合,包含了数据。它是被分区的,分为多个分区,每个分区分布在集群中的不同节点上,从而让RDD中的数据可以被并行操作。(分布式数据集)

RDD的数据默认存放在内存中,但是当内存资源不足时,Spark会自动将RDD数据写入磁盘。RDD的弹性体现在与RDD上自动进行内存和磁盘之间权衡和切换的机制。

4.11 RDD的血缘关系

RDD的血缘(Lineage)会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区;

4.12 宽依赖与窄依赖

宽依赖:
本质就是shuffle。父RDD的每一个partition中的数据,都可能会传输一部分到下一个子RDD 的每一个partition中,此时会出现父RDD和子RDD的partition之间具有交互错综复杂的关系,这种情况就叫做两个RDD之间是宽依赖。(简单理解:父RDD的一个分区会被子RDD的多个分区所依赖)

窄依赖:
父RDD和子RDD的partition之间的对应关系是一对一的。(简单理解:父RDD的一个分区只会被子RDD的1个分区所依赖)

窄依赖:Spark可以对窄依赖进行优化:合并操作,形成pipeline(管道),同一个管道中的各个操作可以由同一个线程执行完,且如果有一个分区数据丢失,只需要从父RDD的对应分区重新计算即可,不需要重新计算整个任务,提高容错;

宽依赖:Spark可以根据宽依赖进行Stage阶段划分,同一个Stage阶段中的都是窄依赖,可以对该阶段内的窄依赖优化;

Spark中的shuffle算子:
(1)去重:distinct;
(2)聚合:reduceByKey、groupBy、groupByKey、aggregateByKey、combineByKey;
(3)排序:sortByKey、sortBy、Ordering
(4)重分区:coalesce、repartition
(5)集合或者表操作:intersection、subtract、subtractByKey、join、leftOuterJoin

4.13 Spark中是如何划分stage,每个stage又是根据什么决定task个数?

Stage:根据RDD之间依赖关系的不同,将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。

Task:Stage是一个 TaskSet,在一个Stage内,最终的RDD有多少个partition,就会产生多少个task。

4.14 为什么需要划分Stage?

由于一个job任务中,可能有大量的宽窄依赖,由于窄依赖不会产生shuffle,宽依赖会产生shuffle。后期划分完Stage之后,在同一个Stage中只有窄依赖,并没有宽依赖,这些窄依赖对应的Task是可以互相独立的去运行,划分完Stage之后,它内部是有很多可以并行运行的Task;

4.15 Spark中的Shuffle

4.15.1 Spark Shuffle的原理

Spark在DAG阶段根据宽依赖(宽依赖的本质就是Shuffle)划分Stage,上游的Stage启动map task,然后每个map task将计算结果数据分成多份,每一份对应到下游Stage的每个partition中,并且将临时计算的结果临时写到磁盘中,这个过程叫做shuffle write;
下游Stage启动reduce task,每个reduce task通过网络拉取上游Stage中所有map task的指定分区的结果数据,这个过程叫做shuffle read,最后完成reduce端的业务处理逻辑;

4.15.2 Spark Shuffle的实现

在Spark中,Shuffle的实现分为两种:HashShuffle和SortShuffle;
(1)HashShuffle
①HashShuffle又分为普通机制和合并机制。
1)普通机制:会产生M(map task的个数)R(reduce task的个数)个数的磁盘小文件,所以会对磁盘的IO读写性能有较大的影响,最后还可能由于大量的磁盘小文件导致出现OOM情况;
2)合并机制:通过重复利用buffer机制,使磁盘小文件的数量尽可能少,但是当Reducer端的并行任务或者是数据分片过多的时候,依然会产生大量的磁盘小文件;
(2)SortShuffle
①SortShuffle也分为普通机制和bypass机制。
1)普通机制,首先在内存(5MB)中完成排序,然后会产生2M(map task的个数)个磁盘小文件。而且当shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold=200参数的值,或者RDD算子不是聚合类的shuffle算子的时候,会触发SortShuffle的bypass机制,SortShuffle的bypass机制不会进行排序,这可以极大的提高了性能;

4.15.3 Spark Shuffle的演进过程

在Spark1.2以前,默认的ShuffleManager计算引擎是HashShuffleManager。HashShuffleManager有着一个严重的弊端,就是会产生大量的中间磁盘文件,进而由大量的磁盘IO操作影响了性能。(优化前下游有一个task他就会生成一个对应的文件,有几个task就会有几个文件,优化后通过复用buffer来优化shuffle过程中产生小文件的数量);

在Spark1.2以后的版本中,默认的ShuffleManager改成了SortShuffleManager。SortShuffleManager相对于HashShuffleManager来说,做了一定的改进。主要就在于,每个task在进行shuffle操作时,虽然也会产生较多的临时文件,但是最后会将所有的临时文件合并(merge)成一个磁盘文件,因此每个task就只有一个磁盘文件,同时还创建一个单独的索引文件。在下一个stage的shuffle read task拉取自己的数据时,只要根据索引读取每个磁盘文件中的部分数据即可。(当shuffle read task的数量小于等于默认的200个时,并且不是聚合类的shuffle算子,就会启动bypass机制,bypass机制并没有对数据进行sort);

Spark中的shuffle算子:
(1)去重:distinct;
(2)聚合:reduceByKey、groupBy、groupByKey、aggregateByKey、combineByKey;
(3)排序:sortByKey、sortBy、Ordering;
(4)重分区:coalesce、repartition;
(5)集合或者表操作:intersection、subtract、subtractByKey、join、leftOuterJoin;

4.15.4 Spark与MapReduce Shuffle的异同

从整体功能上看,其实两者并没有大的差别,都是将mapper(Spark里是ShuffleMapTask)的输出进行分区,然后不同的分区数据被送到不同的reducer中(Spark 里 reducer 可能是下一个 stage 里的 ShuffleMapTask,也可能是 ResultTask)。reducer端以内存作缓冲区,一边shuffle一边聚合(aggregate)数据,等到数据聚合(aggregate)好以后,再进入reduce方法进行处理(Spark 里可能是后续的一系列操作);
从流程的上看,两者差别不小,MapReduce是sort-based,进入combiner和reduce的记录必须先sort。这样的好处在于combiner/reduce可以处理大规模的数据,因为它输入数据可以通过外排得到(mapper对每段数据先做排序,reducer的shuffle对排好序的每段数据做归并)。以前Spark 默认选择的是hash-based,通常使用HashMap来对shuffle来的数据进行合并,不会对数据进行提前排序。如果用户需要经过排序的数据,那么需要自己调用类似 sortByKey的操作。在Spark 1.2之后,sort-based变为默认的Shuffle实现;
从流程实现角度来看,两者也有不少差别,MapReduce将处理流程划分出明显的几个阶段:map、spill、merge、shuffle、sort、reduce等。每个阶段各司其职,可以按照过程式的编程思想来逐一实现每个阶段的功能。在Spark中,没有这样功能明确的阶段,只有不同的stage和一系列的transformation算子,所以spill、merge、aggregate等操作需要蕴含在transformation算子中;

4.16 Spark 中算子reduceByKey与groupByKey的区别

reduceByKey:按照 key 进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是 RDD[k,v]。

groupByKey:按照 key 进行分组,直接进行 shuffle。

开发指导:reduceByKey 比 groupByKey,建议使用。但是需要注意是否会影响业务逻辑。

4.17 Repartition和Coalesce关系与区别

1)关系:
两者都是用来改变RDD的partition数量的,repartition底层调用的就是coalesce方法:coalesce(numPartitions, shuffle = true)

2)区别:
repartition一定会发生shuffle,coalesce根据传入的参数来判断是否发生 shuffle。一般情况下增大rdd的partition数量使用repartition,减少partition数量时使用coalesce。

4.18 Spark手写WordCount统计程序

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object TestSpark {

  def main(args: Array[String]): Unit = {

    // 创建SparkConf并设置App名称和master地址
    val conf = new SparkConf().setAppName("word").setMaster("local[2]")

    // 创建SparkContext,该对象是提交Spark App的入口
    val sc = new SparkContext(conf)

    // 使用sc创建RDD并执行相应的transformation和action
    val rdd: RDD[String] = sc.textFile("/word.txt")
    rdd.flatMap(x => x.split(" ")).map((_, 1)).reduceByKey((_ + _)).saveAsTextFile("/output.txt")

    //关闭链接
    sc.stop()
  }
}

4.19 Spark中的共享变量(广播变量与累加器)的基本原理与应用场景

(1)广播变量
①定义:广播变量就是把变量在所有节点的内存之间进行共享,在每个机器上缓存一个只读的变量,而不是为机器上的每个任务(Tasks)都生成一个副本,这样做可以避免机器内的Executors多次使用网络拉取数据,减少网络的传输,同时减少内存的消耗;
(2)累加器
①定义:累加器可以让多个task共同去操作一份变量,进行累加计算操作,当需要做全局计算时,我们就可以使用累加器;

累加器是Spark中提供的一种分布式的变量机制,其原理类似于MapReduce,即分布式的改变,然后聚合这些改变。累加器一个常见的用途是在调试时对作业执行过程中的事件进行计数。而广播变量用来高效分发较大的对象。

4.20 Spark中的数据倾斜问题和解决的方法

4.20.1 什么是数据倾斜?

数据倾斜是因为某一个partition或某几个partition的数据特别大,导致这几个partition上的计算需要耗费相当长的时间。

4.20.2 数据倾斜是如何造成的?

在Spark中同一个应用程序划分成多个stage,这些stage之间是串行执行的,而一个stage里面的多个task是可以并行执行,task数目由partition数目决定,如果一个partition的数目特别大,那么导致这个task执行时间很长,导致接下来的stage无法执行,从而导致整个job执行变慢。

4.20.3 如何定位Spark数据倾斜问题?

(1)通过Spark Web UI去查看task执行情况,查看到底是在哪个stage阶段下卡住了,然后再去我们的程序代码中,查看涉及到shuffle操作算子有哪些;
(2)如果数据倾斜导致出现了OOM情况,可以通过去Yarn的日志中进行定位查看,看看是执行到第几个stage,哪个stage下的哪个task执行得特别慢,定位到具体的stage之后,再到程序代码中定位到具体发生数据倾斜的算子是哪个;

4.20.4 Spark解决数据倾斜的方案

(1)方案1:抽样统计key的个数
①选取key,对数据进行抽样,统计出每个key出现的次数,根据出现次数大小进行排序,如果发现多数key分布都比较平均,而只有个别的key数据量是非常大的,则说明发生了数据倾斜;
②接着对发生数据倾斜的key进行过滤出来,单独进行处理(添加随机前缀或自定义分区),处理完之后,再与前面的结果进行union操作即可;
(2)方案2:提高shuffle操作的reduce端的并行度
①提高shuffle操作的reduce并行度,增加reduce task的数量,就可以让每个reduce task分配到更少的数据量,这样的话,有很大的可能就可以缓解数据倾斜问题,或者说基本解决掉数据倾斜的问题;
(3)方案3:将reduce join 转换为map join,对小表进行广播
①如果两个数据量差异较大的表做join时,发生了数据倾斜,我们一般将小表广播到每个节点去,这样就可以实现map端join,从而省掉shuffle阶段,避免了大量数据在个别节点上的进行汇聚,执行效率也得到了提升;
(4)方案4:两阶段聚合(局部聚合+全局聚合)
①将原本相同的key通过添加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,这样就解决了单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行一次全局聚合,就可以得到最终的结果;

4.21 Spark Core中常用算子

4.21.1 Transformation算子与Action算子的区别

① 在Spark中Transformation操作算子表示将一个RDD通过一系列操作变为另一个RDD的过程,这个操作可能是简单的加减操作,也可能是某个函数或某一系列函数。值得注意的是Transformation操作算子并不会触发真正的计算,只会建立RDD间的关系图;
② 不同于Transformation操作算子,Action操作算子代表一次计算的结束,不再产生新的 RDD,将结果返回到Driver程序或者输出到外部。所以Transformation操作算子只是建立计算关系,而Action 操作算子才是实际的执行者;

4.21.2 常用Transformation算子

① map
② flatMap
③ filter
④ reduceByKey
⑤ groupByKey

4.21.3 常用Action算子

① reduce
② collect
③ count
④ first
⑤ take
⑥ saveAsTextFile
⑦ foreach

4.22 RDD、DataFrame&DataSet的区别与联系

(1)共性:
①RDD、DataFrame、DataSet都是Spark平台下的分布式弹性数据集,为处理超大型数据提供便利;
②三者都有惰性机制,在进行创建、转换时不会立即执行,只有在遇到Action时,才会开始遍历运算。如果代码里面仅有创建、转换,后面没有任何Action算子,代码不会执行;
③三者都有partition的概念,进行缓存操作,还可以进行检查点操作;
④三者有许多相似的函数,如map、filter、排序等;
⑤在对DataFrame和DataSet进行操作时,很多情况下需要导入import spark.implicits._支持;

(2)区别:
①RDD可以理解为分布式的Java对象的集合;
②DataFrame是一种特殊的RDD,可以类似于数据库中的一张二维数据表,即RDD+schema信息,每一行的类型固定为Row,只有通过解析才能获取各个字段的值;
③DataSet是DataFrame的父类,当DataSet中存储Row时,两者等价(DataFrame=DataSet[Row]);

总结:
DataFrame = RDD[row] + schema;
DataSet = RDD[case class].toDS;

4.23 Spark中3种join的实现

(1)Broadcast Hash Join
①适用的场景:适合一张极小的表(spark.sql.autoBroadcastJoinThreshold=10MB)和一张大表进行Join;
②底层实现:首先将小表的数据先发给driver,driver再统一分将数据发给所有的executor,然后在每个executor上执行 hash join,小表构建为hash table,大表的分区数据去匹配小表hash table中的数据;
③缺点:当我们程序中有非常多的Broadcast Hash Join操作时,对driver的内存来说是一个非常大的考验;
(2)Shuffle Hash Join
①适用的场景:适合一张小表(spark.sql.autoBroadcastJoinThreshold>10MB)和一张大表进行Join;
②底层实现:首先大表、小表分别进行shuffle,但是它们相同的key会在同一个executor中,然后再进行本地的hash join就行;
(3)Shuffle Sort Merge Join
①适用的场景:适合两张大表进行Join;
②底层实现:首先两张大表分别进行shuffle,然后保证相同的key都在一个executor节点上,接着分别再进行sort排序,排完序之后再进行merge;

4.24 Spark SQL的解析流程

正常的SQL执行先会经过SQL Parser解析SQL,然后经过Catalyst优化器进行处理,最后到Spark执行。
Catalyst优化的过程又分为很多个过程,其中包括:
① Analysis:主要利用Catalog信息将Unresolved Logical Plan(未解析逻辑算子树)解析成Analyzed logical plan(解析后的逻辑算子树);
② Logical Optimizations:利用一些Rule(规则)将Analyzed logical plan(解析后的逻辑算子树)解析成Optimized Logical Plan(优化后的逻辑算子树);
③ Physical Planning:前面的logical plan不能被Spark执行,而这个过程是把logical plan转换成多个物理执行计划;
④ physical plans:在多个执行计划中选择最佳的physical plan;
⑤ Code Generation:这个过程会把SQL查询生成Java字节码去执行;

4.25 Spark程序执行spark-submit参数说明

spark-submit \
--class com.sm.liujinhe.job.Idmapping \     # 指定app主类名称,含包名
--master yarn \     # 指定可设置模式:yarn、、yarn-cluster、yarn-client、local
--deploy-mode client \    # Driver程序运行的地方:client、cluster
--driver-memory 4G \     # Driver程序使用内存大小
--num-executors 30 \     # 仅限于Spark on Yarn模式,启动的executor的数量,默认为2
--executor-memory 6G \   # 每个executor内存大小,默认为1G
--executor-cores 3 \    # 仅限于Spark on Yarn模式,每个executor使用的CPU核数,默认为1
--conf spark.default.parallelism=180 \
/liujinhe/jars/idmapping-1.0-SNAPSHOT.jar   # 指定jar包所在的路径

4.26 SparkStreaming的基本原理

首先,接受实时输入数据流,然后将数据拆分成一个个batch,比如每收集一秒的数据封装成一个batch,接着将每个batch交给spark的计算引擎进行处理,最后会生产出一个结果数据流,其中数据也是一个一个的batch组成的;

4.27 SparkStreaming有哪几种方式消费Kafka中的数据,它们之间的区别是什么?

(1)基于Receiver方式
Receiver是使用Kafka的高层次Consumer API来实现的。Receiver从Kafka中获取的数据都是存储在Spark Executor的内存中的(如果突然数据暴增,大量batch堆积,很容易出现内存溢出的问题),然后Spark Streaming启动的job会去处理那些数据。
然而,在默认的配置下,这种方式可能会因为底层的失败而丢失数据。如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming的预写日志机制(Write Ahead Log,WAL)。该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如 HDFS)上的预写日志中。所以,即使底层节点出现了失败,也可以使用预写日志中的数据进行恢复。

(2)基于Direct方式
是在 Spark 1.3中引入的,从而能够确保更加健壮的机制。替代掉使用Receiver来接收数据后,这种方式会周期性地查询Kafka,来获得每个topic+partition的最新的offset,从而定义每个batch的offset 的范围。当处理数据的job启动时,就会使用Kafka的简单consumer api来获取Kafka指定offset范围的数据。

对比:
基于receiver的方式,是使用Kafka的高阶 API 来在ZooKeeper中保存消费过的offset 的。这是消费Kafka数据的传统方式。这种方式配合着WAL机制可以保证数据零丢失的高可靠性,但是却无法保证数据被处理一次且仅一次,可能会处理两次。因为Spark和 ZooKeeper之间可能是不同步的。
基于direct的方式,使用Kafka的简单API,Spark Streaming 自己就负责追踪消费的offset,并保存在checkpoint中。Spark自己一定是同步的,因此可以保证数据是消费一次且仅消费一次。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值