大数据面试(spark)

大数据版本

系统 centos7

内存 256G

核数 24核

磁盘 80T

大数据总共12台服务器

hadoop服务器 12台

yarn服务器 10台

zookeeper服务器 3台

kafka服务器 9台

elasticsearch服务器 11台22节点

探针服务器 10台

hadoop-hdp 2.5.6

zookeeper 3.4.6

kafka 0.10.0.1

scala 2.11

spark 2.3.4

elasticsearch 6.1.0

springboot 2.1.8

一、scala语法

1.scala闭包?

闭包其实是一个函数的返回值依赖于声明在函数外部一个或多个变量。

object Test {  
   def main(args: Array[String]) {  
      println( "muliplier(1) value = " +  multiplier(1) )  
      println( "muliplier(2) value = " +  multiplier(2) )  
   }  
   var factor = 3  //变量
   val multiplier = (i:Int) => i * factor  
}  

2.scala伴生对象?

在同一个scala源文件中,class与object有同样的名字。

object称为伴生对象,class称为伴生类。它们之间可以相互访问private属性。

3.scala伴生对象apply方法?

伴生对象加括号,会直接调用伴生对象里面的 apply 方法

object Test {
  def main(args: Array[String]): Unit = {
    val student =Student()
  }
}
//伴生类
class Student{
  def apply():Unit = {
    println("class Student apply")
  }
  def study():Unit = {
    println("study hard")
  }
}
//伴生对象
object Student{
  def apply():Unit = {
    println("[enter] object student apply")
  }
}

4.scala中var与val?

var可以多次赋值

5.scala柯里化?

将原来接受两个参数的函数变成接受一个参数的函数过程。新函数返回一个原来以第二个参数作为参数的函数。

定义一个函数:
def add(x:Int,y:Int)=x+y
那么我们应用的时候,应该是这样用:add(1,2)

现在我们把这个函数变一下形:
def add(x:Int)(y:Int) = x + y
那么我们应用的时候,应该是这样用:add(1)(2),最后结果都一样是3,这种方式(过程)就叫柯里化。

add(1)(2) 实际上是依次调用两个普通函数(非柯里化函数),接收一个x为参数,返回一个匿名函数,该匿名函数的定义是:接收一个Int型参数y,函数体为x+y

val result = add(1) 
返回一个result,那result的值应该是一个匿名函数:(y:Int)=>1+y
所以为了得到结果,我们继续调用result。
val sum = result(2)
最后打印出来的结果就是3。

6.scala隐式转化和隐式参数?

隐式转换,是指在object中以implicit关键字声明的带有单个参数的方法。自动将某种类型转换为另外一种类型。在需要用到隐式转换的地方, 使用import引入隐式转换。

class RichFile(val file: File) {
  def read = {
      Source.fromFile(file.getPath).mkString
  }
}
 
//创建隐式转换 把当前类型转换成增强的类型
object Context {
    //File --> RichFile
    implicit def file2RichFile(file: File) = new RichFile(file)
}
 
object Hello_Implicit_Conversions {
    def main(args: Array[String]): Unit = {
        //导入隐式转换
        import Context.file2RichFile
        //File类本身没有read方法 通过隐式转换完成
        //这里的read方法是RichFile类中的方法  需要通过隐式转换File --> RichFile
        println(new File("E:\\projectTest\\1.txt").read)
    }
}

在相同作用域下, 可以不用import自动导入隐式转换


class RichFile(val file: File) {
   def read = {
      Source.fromFile(file.getPath).mkString
   }
}


object Hello_Implicit_Conversions {
   def main(args: Array[String]): Unit = {
     //创建隐式转换
     implicit def file2RichFile(file: File) = new RichFile(file)

     //File类本身没有read方法 通过隐式转换完成
     //这里的read方法是RichFile类中的方法 需要通过隐式转换File --> RichFile
     println(new File("E:\\projectTest\\1.txt").read)
   }
}

 

隐式参数, 是指在object中以implicit关键字声明参数列表。调用方法可以不用传implicit修饰参数列表, 编译器会自动查找缺省值提供该方法。可以使用import手动导入隐式参数, 如果在当前作用域则会自动导入。

object Demo {
  //定义一个方法, 这里有一个隐式参数
  def quote(what: String)(implicit delimeter: (String, String)) = {
    delimeter._1 + what + delimeter._2
  }

  //定义一个隐式参数
  object ImplicitDemo {
    implicit val delimeters = ("<<<", ">>>")
  }

  //调用方法测试
  def main(args: Array[String]): Unit = {
    import ImplicitDemo.delimeters

    println(quote("你好"))
  }
}

7.scala匹配模式?

match  {  case1  case2  }

8.scala与java区别?

1)申明变量不同。

2)返回值,scala不用return。

3)通配符,scala_ ,java*。

4)接口,scala为trait继承使用extends,多继承使用with。java为interface继承使用implements多继承用逗号。

9.Scala里trait有什么功能?

Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。

与接口不同的是,它还可以定义属性和方法的实现。

一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。

trait Equal {
var index: String def isEqual(x: Any): Boolean def isNotEqual(x: Any): Boolean
= !isEqual(x) }
trait TraitA{}
trait TraitB{}
trait TraitC{}
object Test1 extends TraitA with TraitB with TraitC{}

 

二、spark算子

spark中的宽依赖和窄依赖?

宽依赖:一个父RDD分区对应多个子RDD分区, 会划分stage。    groupByKey,ruduceByKey

窄依赖:一个父RDD分区对一个子RDD分区,多个父RDD分区对一个子RDD分区。 map,filter

Transformation和action算子有什么区别?

Transformation 变换/转换:这种变换并不触发提交作业,完成作业中间过程处理。Transformation 操作是延迟计算的,也就是说从一个RDD 转换生成另一个 RDD 的转换操作不是马上执行,需要等到有 Action 操作的时候才会真正触发运算(map, filter)

Action 行动算子:这类算子会触发 SparkContext 提交 Job 作业。

Action 算子会触发 Spark 提交任务。(count)

map和mappartitions区别?

map 中的 func 作用的是 RDD 中每一个元素,一对一的将一种数据形式转成另一种数据形式。

而 mapPartitioons 中的 func 作用的对象是 RDD 的一整个分区, 参数和返回数据都是iterator迭代器。

spark中reducebykey与groupbykey区别?

正常情况下两个函数都能得出正确的且相同的结果, 但reduceByKey函数更适合使用在大数据集上,而大多数人建议尽量少用groupByKey,

因为Spark在执行时,reduceByKey先在同一个分区内组合数据,然后在移动。groupByKey则是先移动后组合,所以移动的工作量相对较大。底层基于 combineByKey.

spark中combineByKey?

接受三个参数, createCombiner 转换数据函数, 作用于第一条数据

                       mergeValue 分区上聚合

                       mergeCombiners 把所有分区上的聚合结果再进行聚合

Spark中repartition和coalesce异同?

区别就是repartition一定会触发shuffle,而coalesce默认是不触发shuffle的,他们两个都是RDD的分区进行重新划分,repartition只是coalesce接口中shuffle为true的简易实现。

RDD有多少种持久化方式?memory_only如果内存存储不了,会怎么操作?

cache和persist

memory_and_disk,放一部分到磁盘

Cache和persist有什么区别和联系?

cache调用了persist方法,cache只有一个默认的缓存级别MEMORY_ONLY ,而persist可以根据情况设置其它的缓存级别。

spark的checkpoint机制?

checkpoint 建立检查点, 是用来容错的,当错误发生的时候,可以迅速恢复的一种机制。

SparkStreaming应用程序必须7*24小时运行,因此必须能够适应与应用程序逻辑无关的故障(例如,系统故障,JVM崩溃等)。 为了实现这一点,Spark Streaming需要将DAG中比较重要的中间数据checkpoint到容错存储系统,以便它可以从故障中恢复。

在 Checkpoint 目录中主要存储两种数据:MetaDate checkpointing 和 Data checkpointing。

spark的checkpoint与cache有什么区别?

checkpoint可以保存数据到hdfs可靠的存储中, persist和cache只能存储在内存或者磁盘中。

checkpoint可以存储数据到hdfs可靠存储中, 所以可以斩断RDD依赖链, persist和cache不行出错会根据rdd依赖链进行回溯计算。

checkpoint程序结束后依然存在, persist和cache程序结束后马上被清楚。

 

spark高级特性累加器和广播变量 ?

在集群模式下,driver和executor运行在不同的JVM进程中,发送给每个executor的闭包中的变量是driver端变量的副本。因此,当foreach函数内引用counter时,其实处理的只是driver端变量的副本,与driver端本身的counter无关。driver节点的内存中仍有一个计数器,但该变量对executor是不可见的!executor只能看到序列化闭包的副本。因此,上述例子输出的counter最终值仍然为零,因为counter上的所有操作都只是引用了序列化闭包内的值。在本地模式下,往往driver和executor运行在同一JVM进程中。那么这些闭包将会被共享,executor操作的counter和driver持有的counter是同一个,那么counter在处理后最终值为6。使用累加器解决。

相同点: spark是一个分布式计算框架, 导致变量需要共享性操作。

累加器: 多个节点对一个变量进行共享性的操作。累加器只能在Driver端构建,并只能从Driver端读取结果,在Task端只能进行累加。

广播变量: 算子函数中,使用到了特别大的数据,推荐将该数据进行广播。这样的话,就不至于将一个大数据拷贝到每一个task上去。而是给每个节点进程executor拷贝一份,然后节点上的task共享该数据。这样的话,就可以减少大数据在节点上的内存消耗。并且可以减少数据到 节点的网络传输消耗。

Spark为什么比mapreduce快?

1.RDD缓存,RDD计算是可以设置缓存的,Spark把运算的中间数据存放在内存,迭代计算效率更高。

2.Map缓存,Map的结果也是会被缓存的,以便以后利用

3.Spark容错性好,弹性数据集可以进行计算重建,MR的容错只能重新计算。

4.对Shuffle操作的优化,生成的中间文件减少了,那么磁盘IO就会减少,Spark计算对内存的利用和运行的并行度比MR高。

5.由于MapReduce会对中间数据进行排序,所以Reduce操作必须等到文件全部生成并进行排序之后才可以进行。Spark不是这种自动排序,因此可以生成一点,刷新ShuffleMapTask缓冲区到文件中,然后直接进行Reduce。

6.Spark对于Executor的优化,在JVM虚拟机的基础上对内存弹性利用,使得内存利用率更高。

spark的shuffle 是什么?

Shuffle是Map和Reduce之间的操作,Shuffle 过程本质上就是将 Map 端获得的数据使用分区器进行划分,并将数据发送给对应的 Reducer 的过程。

由于shuffle阶段涉及磁盘的读写和网络IO,因此shuffle性能的高低直接影响整个程序的性能和吞吐量。

在Spark中,只有宽依赖才会进行数据shuffle。

spark的shuffle和Hadoop的shuffle(mapreduce)的联系和区别是什么?

联系: 两者都是用mr模型来进行并行计算, 都是将 mapper的输出进行 partition,不同的 partition 送到不同的 reducer。等到数据 合计好以后进行 reduce()。Spark中的很多计算是作为MapReduce计算框架的一种优化实现。

区别: 从数据流角度:Mapreduce只能从一个map stage接受数据,Spark 可以从多个 Map Stages shuffle 数据(这是 DAG 型数据流中宽依赖的优势,可以表达复杂的数据流操作)。

底层: MapReduce 为了方便对存在于不同 partition 的 key/value Records进行Group,就提前对 key 进行排序,这样做的好处在于 combine/reduce() 可以处理大规模的数据。Spark 认为很多应用不需要对 key 排序。

那些spark算子会有shuffle?

1.去重  distinct

2.排序 groupByKey,reduceByKey

3.重分区 repartition,coalesce

4.集合或者表操作 interection,join

Spark如何处理数据倾斜?

什么是数据倾斜?

数据倾斜是指我们在并行进行数据处理的时候,由于数据Spark的单个Partition)的分布不均,导致大量的数据集中分不到一台或者某几台计算节点上,导致处理速度远低于平均计算速度,从而拖延导致整个计算过程过慢,影响整个计算性能

数据倾斜的危害?

单个或者某几个task拖延整个任务运行时间,导致整体耗时过大。

单个task处理数据过多,很容易导致内存溢出。

数据倾斜的常见解决方法?

没有产生shuffle操作:

1. 数据源文件不均匀, 尽量使用可切割的文本存储,生成尽量多的task进行并行计算。

产生shuffle操作:

并行度---聚合---join

1. 提高shuffle操作的并行度。

增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个task,从而让每个task处理比原来更少的数据,让数据分布更加均匀。

缺点: 该方案通常无法彻底解决数据倾斜,因为如果出现一些极端情况,比如某个key对应的数据量有100万,那么无论你的task数量增加到多少,这个对应着100万数据的key肯定还是会分配到一个task中去处理,因此注定还是会发生数据倾斜的。

2. 局部聚合和全局聚合。

适用场景: 适用于聚合类的shuffle操作, 对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时,比较适用这种方案。

 

这个方案的核心实现思路就是进行两阶段聚合。第一次是局部聚合,先给每个key都打上一个随机数,比如10以内的随机数,此时原先一样的key就变成不一样的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着对打上随机数后的数据,执行reduceByKey等聚合操作,进行局部聚合,那么局部聚合结果,就会变成了(1_hello, 2) (2_hello, 2)。然后将各个key的前缀给去掉,就会变成(hello,2)(hello,2),再次进行全局聚合操作,就可以得到最终结果了,比如(hello, 4)。

 

先给每个key都打上一个随机数, 将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。

3. 将reduce join转为map join。

适用场景:在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,一个大表和一个小表的情况, 比较适用此方案。

普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是map join,此时就不会发生shuffle操作,也就不会发生数据倾斜。

4. 采样倾斜key并分拆join操作。

适用场景:在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,两个大表join, 一个RDD少数几个key的数据量过大, 而另一个RDD中的所有key都分布比较均匀,那么采用这个解决方案是比较合适的。

对包含少数几个数据量过大的key的那个RDD,通过sample算子采样出一份样本来,然后统计一下每个key的数量,计算出来数据量最大的是哪几个key。

然后将这几个key对应的数据从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上100以内的随机数作为前缀,而不会导致倾斜的大部分key形成另外一个RDD。 *接着将需要join的另一个RDD,也过滤出来那几个倾斜key对应的数据并形成一个单独的RDD,

将每条数据膨胀成100条数据,这100条数据都按顺序附加一个0~100的前缀。

再将附加了随机前缀的独立RDD与另一个膨胀n倍的独立RDD进行join,此时就可以将原先相同的key打散成n份,分散到多个task中去进行join了。 * 而另外两个普通的RDD就照常join即可。 * 最后将两次join的结果使用union算子合并起来即可,就是最终的join结果。

5. 使用随机前缀和扩容RDD进行join。

使用场景:两个RDD进行join的时候,如果数据量都比较大, RDD中有大量的key导致数据倾斜。

通过sample算子采样出一份样本来, 对两个RDD中找到造成数据倾斜的RDD, 比如有多个key都对应了超过1万条数据。然后将该RDD的每条数据key都打上一个100以内的随机前缀。 

同时对另外一个相对正常的RDD进行扩容,将每条数据都扩容成100条数据,扩容出来的每条数据都依次打上一个0~100的前缀。最后将两个处理后的RDD进行join即可。

 

三、spark执行流程

Spark的数据本地性有哪几种?

PROCESS_LOCAL: 数据在同一个 JVM 中,即同一个 executor 上。这是最佳数据 locality。
NODE_LOCAL: 数据在同一个节点上。比如数据在同一个节点的另一个 executor上;或在 HDFS 上,恰好有 block 在同一个节点上。速度比 PROCESS_LOCAL 稍慢,因为数据需要在不同进程之间传递或从文件中读取。
NO_PREF: 数据从哪里访问都一样快,不需要位置优先。
RACK_LOCAL: 数据在同一机架的不同节点上。需要通过网络传输数据及文件 IO,比 NODE_LOCAL 慢。
ANY: 数据在非同一机架的网络上,速度最慢。

spark-submit的时候如何引入外部jar包?

spark-submit –jars

spark有哪些组件?

master:管理集群和节点,不参与计算。

worker:计算节点,进程本身不参与计算,和master汇报。

Driver:运行程序的main方法,创建spark context对象。

spark context:控制整个application的生命周期,包括dagsheduler和task scheduler等组件。

client:用户提交程序的入口。

spark集群运行模式?

1.本地模式。运行在一台机器上, 通常用于练手或者测试环境,本地模式分三类:

    local:只启动一个executor

    local[k]:启动k个executor

    local[*]:启动跟cpu数目相同的 executor

2.standalone模式。构建master+slaves的spark集群,多态机器同时部署spark环境。

3.yarn模式。分布式部署集群,资源和任务监控交给yarn管理,包含cluster和client运行模式,

   cluster适合生产,driver运行在集群子节点,具有容错功能,

   client适合调试,dirver运行在客户端(提交任务的机器)

4.Mesos模式。官方推荐这种模式, 但国内很少人用.

spark 物理执行流程?

1.客户端先把spark程序提交给driver,driver负责运行spark程序的main方法,运行完后根据rdd依赖关系生成逻辑执行图,然后把逻辑执行图调用Action算子执行。

2.SparkContext创建DAGScheduler,根据每个job中rdd依赖关系生成DAG有向无环图,会根据宽依赖生成stage并划分成不同阶段,每个阶段对应一个TaskSet。

3.DAGScheduler请求SparkContext创建的TaskScheduler调度TaskSet,TaskScheduler会询问集群有多少资源,资源返回给TaskScheduler。

4.TaskScheduler把stage调度到executor中执行taskSet,得到结果再发送给driver。

spark物理执行图基本概念?

Application:编写的Spark的应用程序。

Driver:Spark中的Driver负责运行上述Application的main函数并创建SparkContext,创建SparkContext的目的是为了准备Spark应用程序的运行环境,在Spark中有SparkContext负责与ClusterManager通信,进行资源申请、任务的分配和监控当Executor部分运行完毕后,Driver同时负责将SparkContext关闭。

Executor:进程——运行在工作节点上,负责运行Task。

DAG: 有向无环图。用户提交的应用程序,Spark底层会根据宽依赖、窄依赖自动生成DAG。反应出RDD之间的依赖关系。

DAGScheduler: DAGscheduler是一个由SparkContext创建,运行在driver上的组件,根据每个job中rdd依赖关系生成DAG。将stage中生成的taskset提交给TaskScheduler。TaskScheduler负责taskset调度,在executor执行taskset。得到结果再发送给driver。

job:用户提交的作业。spark程序放在集群中运行,job是最大单位,将job拆分为stage和task去调度执行,一个job就是一个spark程序从 读取 - 计算 - 运行的过程。 spark任务调用一次Action算子生成一个job。

 一个spark程序可能有多个job,job之间是串行的,第二个job需要等到第一个job执行结束才会执行。

Stage:是Job的基本调用单元,Job根据宽窄依赖划分不同的Stage,一个job包含一个或多个stage,一个Stage中包含一个或者多个同种Task。

task:Executor的工作单元。一个Stage内只会存在同一种Task,Task数量与Stage的Partition数量保持一致(运行的Task数量可能会大于Partition数量)。

一个Application由一个Driver和若干个Job构成,一个Job由多个Stage构成,一个Stage由多个没有Shuffle关系的Task组成。

 

四、spark优化

1.运行资源调优。合理分配executor个数, 内存大小, 核数, driver进程内存, 增大spark task并行度合理分配资源防止资源浪费。

2.内存溢出调优。

   Driver端: 通常由于计算过大的结果集被回收到Driver端导致,需要调大Driver端的内存解决,或者进一步减少结果集的数量。

   Executor端: 解决方法可以增加partition的数量(即task的数量)来减少每个task要处理的数据。

3.程序开发调优。

  *尽可能复用同一个RDD

  *对多次使用的RDD进行持久化cache

  *尽量避免使用shuffle算子

  *使用高性能的算子,mapPartitions替代普通map;使用foreachPartitions替代foreach; 用reduceByKey或者aggregateByKey算子替代groupByKey

  *使用广播变量

  *优化数据结构,尽量使用字符串代替对象,尽量用基本数据类型(Int,Long..)代替字符串,尽量数组代替集合,这样可以尽可能减少内存占用,

4.shuffle数据倾斜优化

5.shuffle优化

      shuffle管理器SortShuffleManager, 两种机制: 普通机制, by pass机制

      区别: 第一,磁盘写机制不同;第二,不会进行排序。也就是说,启用该机制的最大好处在于,shuffle write过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销。

      1.shuffle参数调优

6.Locality Level 数据本地性, spark中task均匀分布在executor中.  存储和计算(即datanode和nodemanager)要么完全分开独立部署,要么完全部署在一起,不要一部分分开部署,一部分部署在一起,如果一定要这样,不要开启数据本地化特性;spark.locality.wait=0

7.JVM层面调优,增大推外内存.

 

五、spark streaming和structured streaming和flink区别

 Spark Streaming接收流数据,并根据一定的时间间隔拆分成一批批batch数据,实际采用微批处理方法。

 Structured Streaming将实时流抽象成一张无边界的表,输入的每一条数据当成输入表的一个新行,同时将流式计算的结果映射为另外一张表,完全以结构化的方式去操作流式数据。

虽说Structured Streaming也有类似于Spark Streaming的间隔,其本质概念是不一样的。Structured Streaming更像流模式。

Structured Streaming是Spark2.0版本提出的新的实时流框架(2.0和2.1是实验版本,从Spark2.2开始为稳定版本),相比于Spark Streaming,优点如下:

1. 同样能支持多种数据源的输入和输出,参考如上的数据流图。

2. 以结构化的方式操作流式数据,能够像使用Spark SQL处理离线的批处理一样,处理流数据,代码更简洁,写法更简单。

3. 基于Event-Time,相比于Spark Streaming的Processing-Time更精确,更符合业务场景。

4. 解决了Spark Streaming存在的代码升级,DAG图变化引起的任务失败,无法断点续传的问题(Spark Streaming的硬伤!!!)

 flink

 flink是标准的实时处理引擎,而且Spark的两个模块Spark Streaming和Structured Streaming都是基于微批处理的。

 Flink是一行一行处理,而Spark是基于数据片集合(RDD)进行小批量处理,所以Spark在流式处理方面,不可避免增加一些延时。Flink的流式计算跟Storm性能差不多,支持毫秒级计算,而Spark则只能支持秒级计算。

 

posted @ 2020-04-26 18:49  所向披靡zz  阅读(563)  评论(0编辑  收藏  举报