spark综合问题

1.什么是spark?
spark是基于内存计算的通用大数据并行计算框架,是一个快速、通用可扩展的大数据分析引擎。它给出了大一统的软件开发栈,适用于不同场合的分布式场景,如批处理、迭代算法、交互式查询、流处理、机器学习和图计算。
2.Spark生态系统?
SparkCore:spark的核心计算 主要Rdd
SparkSQL:提供了类sql方式操作结构化半结构化数据。对历史数据进行交互式查询。(即席查询:用户根据自己的需求,自定义查询)

SparkStreaming:提供了近乎实时的流式数据处理,与storm相比有更高的吞吐量。(实时计算 目前实时计算框架有哪些? storm、sparkstreaming、flink)

SparkMl:提供了常见的机器学习算法库,包括分类、回归、聚类、协同工过滤(个性推荐:用户画像)等,还提供模型评估、数据处理等额外功能,使得机器学习能够更加方便的在分布式大数据环境下,快速的对数据进行处理形成模型后提供在线服务。

Graphx:用来操作图的程序库,可以进行并行的图计算。支持各种常见的图算法,包括page rank、Triangle Counting等。
3.常见的 分布式文件系统?
hdfs fastdfs Tachyon TFS GFS S3
4.master资源分配有哪些?
尽量集中 尽量打散
1.Application的调度算法有两种,一种是spreadOutApps,另一种是非spreadOutApps。

2.spreadOutApps,会将每个Application要启动的executor都平均分配到各个worker上去。(比如有10个worker,20个cpu core要分配,那么实际会循环两遍worker,每个worker分配一个core,最后每个worker分配了2个core,这里的executor数量可能会与spark-submit设置的不一致)

3.非spreadOutApps,将每个Application尽可能分配到尽量少的worker上去。(比如总共有10个worker,每个有10个core,app总共要分配20个core,其实只会分配到两个worker上,每个worker占满10个core,其余app只能分配到下一个worker,这里的executor数量可能会与spark-submit设置的不一致)
5.spark 可以代替hadoop 吗?
spark会替代Hadoop的一部分,会替代Hadoop的计算框架,如mapReduce、Hive查询引擎,但spark本身不提供存储,所以spark不能完全替代Hadoop。
6.spark 特点?
6.1 速度快
Spark 使用DAG 调度器、查询优化器和物理执行引擎,能够在批处理和流数据获得很高的性能。根据官方的统计,它的运算速度是hadoop的100x倍
6.2 使用简单
Spark的易用性主要体现在两个方面。一方面,我们可以用较多的编程语言来写我们的应用程序,比如说Java,Scala,Python,R 和 SQL;另一方面,Spark 为我们提供了超过80个高阶操作,这使得我们十分容易地创建并行应用,除此之外,我们也可以使用Scala,Python,R和SQL shells,以实现对Spark的交互
6.3 通用性强
与其说通用性高,还不如说它集成度高,如图所示:以Spark为基础建立起来的模块(库)有Spark SQL,Spark Streaming,MLlib(machine learning)和GraphX(graph)。我们可以很容易地在同一个应用中将这些库结合起来使用,以满足我们的实际需求。
6.4 到处运行
Spark应用程度可以运行十分多的框架之上。它可以运行在Hadoop,Mesos,Kubernetes,standalone,或者云服务器上。它有多种多种访问源数据的方式。可以用standalone cluster模式来运行Spark应用程序,并且其应用程序跑在Hadoop,EC2,YARN,Mesos,或者Kubernates。对于访问的数据源,我们可以通过使用Spark访问HDFS,Alluxio,Apache Cassandra,HBase,Hive等多种数据源。
7.DAG

有向无环图
"有向"指的是有方向,准确的说应该是同一个方向,"无环"则指够不成闭环。在DAG中,没有区块的概念,他的组成单元是一笔笔的交易,每个单元记录的是单个用户的交易,这样就省去了打包出块的时间。验证手段则依赖于后一笔交易对前一笔交易的验证,换句话说,你要想进行一笔交易,就必须要验证前面的交易,具体验证几个交易,根据不同的规则来进行。这种验证手段,使得DAG可以异步并发的写入很多交易,并最终构成一种拓扑的树状结构,能够极大地提高扩展性。

8.spark 开发语言选择?
java scala python
9.什么是Rdd?
Spark 中最基本的数据抽象是 RDD。

RDD:弹性分布式数据集 (Resilient Distributed DataSet)。
RDD 有三个基本特性

这三个特性分别为:分区,不可变,并行操作。
a, 分区

每一个 RDD 包含的数据被存储在系统的不同节点上。逻辑上我们可以将 RDD 理解成一个大的数组,数组中的每个元素就代表一个分区 (Partition) 。

在物理存储中,每个分区指向一个存储在内存或者硬盘中的数据块 (Block) ,其实这个数据块就是每个 task 计算出的数据块,它们可以分布在不同的节点上。

所以,RDD 只是抽象意义的数据集合,分区内部并不会存储具体的数据,只会存储它在该 RDD 中的 index,通过该 RDD 的 ID 和分区的 index 可以唯一确定对应数据块的编号,然后通过底层存储层的接口提取到数据进行处理。

在集群中,各个节点上的数据块会尽可能的存储在内存中,只有当内存没有空间时才会放入硬盘存储,这样可以最大化的减少硬盘 IO 的开销。
b,不可变

不可变性是指每个 RDD 都是只读的,它所包含的分区信息是不可变的。由于已有的 RDD 是不可变的,所以我们只有对现有的 RDD 进行转化 (Transformation) 操作,才能得到新的 RDD ,一步一步的计算出我们想要的结果。

这样会带来这样的好处:我们在 RDD 的计算过程中,不需要立刻去存储计算出的数据本身,我们只要记录每个 RDD 是经过哪些转化操作得来的,即:依赖关系,这样一方面可以提高计算效率,一方面是错误恢复会更加容易。如果在计算过程中,第 N 步输出的 RDD 的节点发生故障,数据丢失,那么可以根据依赖关系从第 N-1 步去重新计算出该 RDD,这也是 RDD 叫做"弹性"分布式数据集的一个原因。
c,并行操作

因为 RDD 的分区特性,所以其天然支持并行处理的特性。即不同节点上的数据可以分别被处理,然后生成一个新的 RDD。
10.Rdd的属性?

  • 1) A list of partitions

一个分区列表,一个rdd有多个分区,后期spark任务计算是以分区为单位,一个分区就对应上一个task线程。 通过val rdd1=sc.textFile(文件) 如果这个文件大小的block个数小于等于2,它产生的rdd的分区数就是2 如果这个文件大小的block个数大于2,它产生的rdd的分区数跟文件的block相同

  • 2)A function for computing each split

由一个函数计算每一个分片 比如: rdd2=rdd1.map(x=>(x,1)) ,这里指的就是每个单词计为1的函数

  • 3)A list of dependencies on other RDDs

一个rdd会依赖于其他多个rdd,这里就涉及到rdd与rdd之间的依赖关系,后期spark任务的容错机制就是根据这个特性而来。 比如: rdd2=rdd1.map(x=>(x,1)) rdd2的结果是通过rdd1调用了map方法生成,那么rdd2就依赖于rdd1的结果 对其他RDD的依赖列表,依赖还具体分为宽依赖和窄依赖,但并不是所有的RDD都有依赖。

  • 4)Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)

(可选项) 对于kv类型的rdd才会有分区函数(必须要产生shuffle),对于不是kv类型的rdd分区函数是None。 分区函数的作用:它是决定了原始rdd的数据会流入到下面rdd的哪些分区中。 spark的分区函数有2种:第一种hashPartitioner(默认值), 通过 key.hashcode % 分区数=分区号 第二种RangePartitioner,是基于一定的范围进行分区。

  • 5)Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)

(可选项) 一组最优的数据块的位置,这里涉及到数据的本地性和数据位置最优 spark后期在进行任务调度的时候,会优先考虑存有数据的worker节点来进行任务的计算。大大减少数据的网络传输,提升性能。
11.分片的个数由什么决定?

默认 是手动 ,外部的可从hdfs kafka

text文件分片(sc.textFile为例):

def textFile(
  path: String,
  minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
assertNotStopped()
hadoopFile(path, classOf[TextInputFormat]  /* 数据文件的输入格式 :org.apache.hadoop.mapred.TextInputFormat */
, classOf[LongWritable], classOf[Text],
  minPartitions).map(pair => pair._2.toString).setName(path)
}
 hadoopFile方法生成HadoopRDD
 HadoopRDD(
  this,
  confBroadcast,
  Some(setInputPathsFunc),
  inputFormatClass,
  keyClass,
  valueClass,
  minPartitions) /* minPartitions为生成该RDD的最小分片数,表示该RDD的分片数最小值,默认为2*/
  .setName(path)

在执行action方法(如count)时,spark应用才真正开始计算,通过调用rdd.partitions.length计算出分片数

    def runJob[T, U: ClassTag](rdd: RDD[T], func: Iterator[T] => U): Array[U] = {
runJob(rdd, func, 0 until rdd.partitions.length)
}

通过跟踪该方法可以看出该函数最终会调用到HadoopRDD的getPartitions方法,在该方法中通过inputFormat的getSplit方法计算分片数

    getInputFormat(jobConf).getSplits(jobConf, minPartitions)

TextInputFormat继承至FileInputFormat,FileInputFormat的getSplit方法网上有许多分析,这里不再展开,大致的原理是根据文件个数,传入的minpartitions,mapreduce.input.fileinputformat.split.minsize等参数计算出分片数。

hbase表分片

在读取HBase数据时,没有类似textFile的接口的封装,可调用如下接口生成给予hbase数据的RDD,

val hBaseRDD = sc.newAPIHadoopRDD(conf, 
classOf[TableInputFormat], /*该类的全类名为:  org.apache.hadoop.hbase.mapreduce.TableInputFormat */
  classOf[org.apache.hadoop.hbase.io.ImmutableBytesWritable],  
  classOf[org.apache.hadoop.hbase.client.Result])  
  
  该方法生成new NewHadoopRDD(this, fClass, kClass, vClass, jconf)

在执行action操作时,同样调用到rdd.partitions方法,跟踪至newHadoopRDD之后,发现调用到

inputFormat.getSplits(new JobContextImpl(_conf, jobId))

查看对应的getSplits方法可以看出:

默认情况下(hbase.mapreduce.input.autobalance的值为false)hbase表如果存在多个region,则每个region设置为一个split。

如果设置了开启均衡(设置hbase.mapreduce.input.autobalance的值为true:在hbase的region大小不均衡引发的数据倾斜,将导致不同的region处理耗时较多,该参数为了解决此场景),则会在每个region对应一个split的基础上,将较小(小于平均大小)的region进行合并作为一个split,较大(大于平均size的三倍(其中三可配置))的region拆分为两个region。

   splits伪代码如下(源码可参考TableInputFormatBase.calculateRebalancedSplits):
    
    while ( i < splits.size)
    {
        if(splits(i).size > averagesize * 3) {
        if(! splitAble)
            resultsplits.add(split(i))
        else{
            (split1,split2) = Split(splits(i))
            resultsplits.add(split1)
            resultsplits.add(split2)
        }
        i++ 
        }
        else if(splits(i).size > averagesize) {
            resultsplits.add(split(i))
            i++
        }else{
            startKey = split(i).getStartRow
            i++;
            while(totalSize + splits(i).size < averagesize * 3){
                totalSize += splits(i).size
                endKey = splits(i).getEndRow
            }
            resultsplits.add(new TableSplit(startKey,endKey,*))
        }
    }

Kafka数据的分片

Spark框架在读取Kafka消息时,将Kafka数据抽象为KafkaRDD(SparkStreaming)或者KafkaSourceRDD(StructedStreaming),查看对应RDD的getPartitions方法和定义:
KafkaSourceRDD:

    override def getPartitions: Array[Partition] = {
offsetRanges.zipWithIndex.map { case (o, i) => new KafkaSourceRDDPartition(i, o) }.toArray
 }
 offsetRanges的数据结构为
 private[kafka010] case class KafkaSourceRDDOffsetRange(
topicPartition: TopicPartition,
fromOffset: Long,
untilOffset: Long,
preferredLoc: Option[String]) 

可以看出partition个数为对应的TopicPartition的个数
KafkaRDD

override def getPartitions: Array[Partition] = {
    offsetRanges.zipWithIndex.map { case (o, i) =>
        new KafkaRDDPartition(i, o.topic, o.partition, o.fromOffset, o.untilOffset)
    }.toArray
  }
  offsetRanges数据结构为:
  final class OffsetRange private(
        val topic: String,
        val partition: Int,
        val fromOffset: Long,
        val untilOffset: Long) 

可以看出partition个数为对应的partition的个数
12.分片的意义?
增加并行度
当然也不是并行越高越好,根据资源而定
13.spark 的容错?
1、Lineage机制
Lineage简介

相比其他系统的细颗粒度的内存数据更新级别的备份或者LOG机制,RDD的Lineage记录的是粗颗粒度的特定数据Transformation操作(如filter、map、join等)行为。当这个RDD的部分分区数据丢失时,它可以通过Lineage获取足够的信息来重新运算和恢复丢失的数据分区。因为这种粗颗粒的数据模型,限制了Spark的运用场合,所以Spark并不适用于所有高性能要求的场景,但同时相比细颗粒度的数据模型,也带来了性能的提升。
2、Checkpoint机制

我们应该都很熟悉 checkpoint 这个概念, 就是把内存中的变化刷新到持久存储,斩断依赖链 在存储中 checkpoint 是一个很常见的概念, 举几个例子

  • 数据库 checkpoint 过程中一般把内存中的变化进行持久化到物理页, 这时候就可以斩断依赖链, 就可以把 redo 日志删掉了, 然后更新下检查点,

  • hdfs namenode 的元数据 editlog, Secondary namenode 会把 edit log 应用到 fsimage, 然后刷到磁盘上, 也相当于做了一次 checkpoint, 就可以把老的 edit log 删除了。

  • spark streaming 中对于一些 有状态的操作, 这在某些 stateful 转换中是需要的,在这种转换中,生成 RDD 需要依赖前面的 batches,会导致依赖链随着时间而变长。为了避免这种没有尽头的变长,要定期将中间生成的 RDDs 保存到可靠存储来切断依赖链, 必须隔一段时间进行一次进行一次 checkpoint。

cache 和 checkpoint 是有显著区别的, 缓存把 RDD 计算出来然后放在内存中, 但是RDD 的依赖链(相当于数据库中的redo 日志), 也不能丢掉, 当某个点某个 executor 宕了, 上面cache 的RDD就会丢掉, 需要通过 依赖链重放计算出来, 不同的是, checkpoint 是把 RDD 保存在 HDFS中, 是多副本可靠存储,所以依赖链就可以丢掉了,就斩断了依赖链, 是通过复制实现的高容错。但是有一点要注意, 因为checkpoint是需要把 job 重新从头算一遍, 最好先cache一下, checkpoint就可以直接保存缓存中的 RDD 了, 就不需要重头计算一遍了, 对性能有极大的提升。
简单来说,就是把重要的Rdd存储在hdfs中,与lineage相比,省时
14.创建Rdd的方式?

  1. 由一个已经存在的Scala集合创建。
val rdd: RDD[Int] = sc.parallelize(Arrscay(1,2,3,4,5))

  1. 由外部存储系统的文件创建。
    包括本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、Cassandra、HBase等。
val rdd2 = sc.textFile("/words.txt")

  1. 已有的RDD经过算子转换生成新的RDD
 val rdd3=rdd2.flatMap(_.split(" "))

15.spark 算子分为? 区别? 写出10个以上常用的算子
1.从大的方向来说,spark算子可以分两类:
1)Transformation 变换/转换算子:这种变换并不触发提交作业,完成作业中间过程处理。Transformation 操作是延迟计算的,也就是说从一个RDD 转换生成另一个 RDD 的转换操作不是马上执行,需要等到有 Action 操作的时候才会真正触发运算。
2)Action算子:Action 行动算子:这类算子会触发 SparkContext 提交 Job 作业。Action 算子会触发 Spark 提交作业(Job),并将数据输出 Spark系统。
2.从小的方向分,可以分为三类:
1)Value数据类型的Transformation算子,这种变换并不触发提交作业,针对处理的数据项是Value型的数据。
2)Key-Value数据类型的Transfromation算子,这种变换并不触发提交作业,针对处理的数据项是Key-Value型的数据对。
3)Action算子,这类算子会触发SparkContext提交Job作业。
常见算子:
spark常见算子
16.如何产生job?
Spark的Job来源于用户执行action操作,就是从RDD中获取结果的操作,而不是将一个RDD转换成另一个RDD的transformation操作。
17.map 和mappartition 的区别?

  • map():每次处理一条数据

  • mapPartition():每次处理一个分区的数据,这个分区的数据处理完后,原RDD中分区的数据才能释放,可能导致OOM

    当内存空间较大的时候建议使用mapPartition(),以提高处理效率

18.repartition 和 coalesce 的区别?

  • coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定
  • repartition实际上是调用的coalesce,默认是进行shuffle的
    一般情况下,repartition是 加分区操作,coalesce 是减分区操作

19.什么是宽依赖 窄依赖?
宽依赖:父RDD的分区被子RDD的多个分区使用 例如 groupByKey、reduceByKey、sortByKey等操作会产生宽依赖,会产生shuffle
窄依赖:父RDD的每个分区都只被子RDD的一个分区使用 例如map、filter、union等操作会产生窄依赖
20.宽依赖的算子有哪些?
bykey的算子、repartition、部分join算子
21.reduceByKey aggregateByKey combineByKey 区别?

  • aggregateByKey()是先对每个partition中的数据根据不同的Key进行aggregate,然后将结果进行shuffle,完成各个partition之间的aggregate。
  • reduceByKey()也是先在单台机器中计算,再将结果进行shuffle,减小运算量
  • combineByKey 与aggregateByKey类似,都调用了combineByKeyWithClassTag,在aggregateByKey中的

22.使用什么可以代替 join ?
广播变量 + map + filter
23.持久化?
RDD通过persist方法或cache方法可以将前面的计算结果缓存,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空间中。
但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
在这里插入图片描述
通过查看源码发现cache最终也是调用了persist方法,默认的存储级别都是仅在内存存储一份,Spark的存储级别还有好多种,存储级别在object StorageLevel中定义的。
在这里插入图片描述
24.容错?
lineage、checkpoint
25.共享变量?
累加器
累加器用来对信息进行聚合,通常在向 Spark传递函数时,比如使用 map() 函数或者用 filter() 传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。如果我们想实现所有分片处理时更新共享变量的功能,那么累加器可以实现我们想要的效果。
系统累加器
针对一个输入的日志文件,如果我们想计算文件中所有空行的数量,我们可以编写以下程序:

scala> val notice = sc.textFile("./NOTICE")
notice: org.apache.spark.rdd.RDD[String] = ./NOTICE MapPartitionsRDD[40] at textFile at <console>:32

scala> val blanklines = sc.accumulator(0)
warning: there were two deprecation warnings; re-run with -deprecation for details
blanklines: org.apache.spark.Accumulator[Int] = 0

scala> val tmp = notice.flatMap(line => {
     |    if (line == "") {
     |       blanklines += 1
     |    }
     |    line.split(" ")
     | })
tmp: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[41] at flatMap at <console>:36

scala> tmp.count()
res31: Long = 3213

scala> blanklines.value
res32: Int = 171

累加器的用法如下所示。
通过在驱动器中调用SparkContext.accumulator(initialValue)方法,创建出存有初始值的累加器。返回值为 org.apache.spark.Accumulator[T] 对象,其中 T 是初始值 initialValue 的类型。Spark闭包里的执行器代码可以使用累加器的 += 方法(在Java中是 add)增加累加器的值。 驱动器程序可以调用累加器的value属性(在Java中使用value()或setValue())来访问累加器的值。
注意:工作节点上的任务不能访问累加器的值。从这些任务的角度来看,累加器是一个只写变量。
对于要在行动操作中使用的累加器,Spark只会把每个任务对各累加器的修改应用一次。因此,如果想要一个无论在失败还是重复计算时都绝对可靠的累加器,我们必须把它放在 foreach() 这样的行动操作中。转化操作中累加器可能会发生不止一次更新
自定义累加器
自定义累加器类型的功能在1.X版本中就已经提供了,但是使用起来比较麻烦,在2.0版本后,累加器的易用性有了较大的改进,而且官方还提供了一个新的抽象类:AccumulatorV2来提供更加友好的自定义类型累加器的实现方式。实现自定义类型累加器需要继承AccumulatorV2并至少覆写下例中出现的方法,下面这个累加器可以用于在程序运行过程中收集一些文本类信息,最终以Set[String]的形式返回。1

package com.atguigu.spark

import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.JavaConversions._

class LogAccumulator extends org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]] {
  private val _logArray: java.util.Set[String] = new java.util.HashSet[String]()

  override def isZero: Boolean = {
    _logArray.isEmpty
  }

  override def reset(): Unit = {
    _logArray.clear()
  }

  override def add(v: String): Unit = {
    _logArray.add(v)
  }

  override def merge(other: org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]]): Unit = {
    other match {
      case o: LogAccumulator => _logArray.addAll(o.value)
    }

  }

  override def value: java.util.Set[String] = {
    java.util.Collections.unmodifiableSet(_logArray)
  }

  override def copy():org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]] = {
    val newAcc = new LogAccumulator()
    _logArray.synchronized{
      newAcc._logArray.addAll(_logArray)
    }
    newAcc
  }
}

// 过滤掉带字母的
object LogAccumulator {
  def main(args: Array[String]) {
    val conf=new SparkConf().setAppName("LogAccumulator")
    val sc=new SparkContext(conf)

    val accum = new LogAccumulator
    sc.register(accum, "logAccum")
    val sum = sc.parallelize(Array("1", "2a", "3", "4b", "5", "6", "7cd", "8", "9"), 2).filter(line => {
      val pattern = """^-?(\d+)"""
      val flag = line.matches(pattern)
      if (!flag) {
        accum.add(line)
      }
      flag
    }).map(_.toInt).reduce(_ + _)

    println("sum: " + sum)
    for (v <- accum.value) print(v + "")
    println()
    sc.stop()
  }
}

广播变量
广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个Spark操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,甚至是机器学习算法中的一个很大的特征向量,广播变量用起来都很顺手。 在多个并行操作中使用同一个变量,但是 Spark会为每个任务分别发送。

scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(35)

scala> broadcastVar.value
res33: Array[Int] = Array(1, 2, 3)

使用广播变量的过程如下:
(1) 通过对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T] 对象。 任何可序列化的类型都可以这么实现。
(2) 通过 value 属性访问该对象的值(在 Java 中为 value() 方法)。
(3) 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。
26.spark 提交方式? 区别?

  • client模式:
    在这里插入图片描述
  • cluster模式:
    在这里插入图片描述
    client模式与cluster模式的区别:
  1. 启动集群nodeManager向ResourceManager汇报资源。

  2. RS掌握了集群资源。

  3. 客户端提交application。

  4. 客户端向ResourceManager申请启动ApplicationMaster。

  5. ResourceManager收到请求之后随即找到一台NodeManager结点启动ApplicationMaster。

  6. ApplicationMaster启动之后向ResourceManager申请资源用于启动Executor。

  7. ResourceManager将申请到的Executor的节点NodeManager返回给ApplicationMaster。

  8. ApplicationMaster连接NodeManager启动Executor。

  9. Executor启动字后反向注册给ApplicationMaster

  10. ApplicationMaster发送task到Executor执行并监控task执行回收结果。
    注意:
    client模式提交任务,会在客户端看到task的执行情况和结果,当在客户端提交多个application时,每个application都会启动自己的Driver,Driver与集群Worker有大量的通信,会造成客户端网卡流量激增问题。这种模式适用于程序测试。不适用于生产环境。

    模式提交任务,Driver会在急群众随机一台Worker上启动,如果提交多个application时,那么每个application的Driver会分散到集群的Worker节点,相当于将client模式的客户端网卡流量激增问题分散到集群中。这种模式适用于生产环境。

    因为cluster模式,随机分散在Worker节点上创建Driver,由Driver来发送任务到Worker。所以打包的程序任务必须在分散的Worker节点对应的目录下都存在。
    27.spark 资源调度方式?
    本地模式:
    在这里插入图片描述
    standalone模式
    在这里插入图片描述
    此模式是spark自带的集群模式
    yarn模式:
    Spark客户端直接连接Yarn,不需要额外构建Spark集群。有yarn-client和yarn-cluster两种模式,主要区别在于:Driver程序的运行节点。
    yarn-client:Driver程序运行在客户端,适用于交互、调试,希望立即看到app的输出
    yarn-cluster:Driver程序运行在由RM(ResourceManager)启动的AP(APPMaster)适用于生产环境。
    在这里插入图片描述

28.spark 查看日志的方式?
日志聚合:较为常用
分散查看:比较麻烦,到每个节点lo文件查看
webui:到网页查看
29.spark 优化?
(硬件 配置 代码)
请看小弟的
大数据优化之spark篇
30.JVM ?
JVM 是 java虚拟机,是用来执行java文件的
包括堆 栈 方法区
栈:里面有参数 | 局部变量 | 返回地址
8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配[局部变量]
堆:对象本身、全局变量 [ 成员变量 ]
堆内存内部结构

①. 所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx 和Xms来控制

②. 堆被划分为新生代和老年代,新生代又被进一步划分为Eden(伊甸园)和Survivor(幸存者)区,老年代,在后面不是堆区是永恒代(方法区)
方法区的概述

①. 所有Java虚拟机线程共享的区域,存储了类结构相关的信息 [ 成员变量、方法、构造器 ]等

②. 方法区的创建是在Java 虚拟机启动时被创建

③. 方法区时逻辑上时堆的一个组成部分,但是在不同虚拟机里头实现是不一样的,最典型的就是永久代(PermGen space)和元空间(Metaspace)
注意:方法区时一种规范,而永久代和元空间是它的一种实现方式

④. 方法区存在OOM现象

存储的东西:
静态变量+常量+类信息+运行时常量池存在方法区中 

常量池和运行时常量池

①. 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息

②. 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

③. 进入class的目录,javap -v 类.class
二进制字节码(类基本信息+常量池+类的方法定义

31.GC的策略?
请参考此文
32.spark 序列化?
有java和kyro,默认是java,但是使用后者更加迅速
KryoSerialization速度快,可以配置为任何org.apache.spark.serializer的子类。但Kryo也不支持所有实现了 java.io.Serializable 接口的类型,它需要你在程序中 register 需要序列化的类型,以得到最佳性能。
33.spark 配置优先级?
配置文件 < 脚本 < 代码
34.sparkStreagming 和storm 对比?
两者并无高下之分,只是应用场景不同。
在这里插入图片描述
两者对比
Spark Streaming仅仅在吞吐量上比Storm要优秀,而吞吐量这一点,也是历来挺Spark Streaming,贬Storm的人着重强调的。但是问题是,是不是在所有的实时计算场景下,都那么注重吞吐量?不尽然。因此,通过吞吐量说Spark Streaming强于Storm,不靠谱。

事实上,Storm在实时延迟度上,比Spark Streaming就好多了,前者是纯实时,后者是准实时。而且,Storm的事务机制、健壮性 / 容错性、动态调整并行度等特性,都要比Spark Streaming更加优秀。

Spark Streaming,有一点是Storm绝对比不上的,就是:它位于Spark生态技术栈中,因此Spark Streaming可以和Spark Core、Spark SQL无缝整合,也就意味着,我们可以对实时处理出来的中间数据,立即在程序中无缝进行延迟批处理、交互式查询等操作。这个特点大大增强了Spark Streaming的优势和功能。
使用场景
对于Storm来说:
1、建议在那种需要纯实时,不能忍受1秒以上延迟的场景下使用,比如实时金融系统,要求纯实时进行金融交易和分析
2、此外,如果对于实时计算的功能中,要求可靠的事务机制和可靠性机制,即数据的处理完全精准,一条也不能多,一条也不能少,也可以考虑使用Storm
3、如果还需要针对高峰低峰时间段,动态调整实时计算程序的并行度,以最大限度利用集群资源(通常是在小型公司,集群资源紧张的情况),也可以考虑用Storm
4、如果一个大数据应用系统,它就是纯粹的实时计算,不需要在中间执行SQL交互式查询、复杂的transformation算子等,那么用Storm是比较好的选择

对于Spark Streaming来说:
1、如果对上述适用于Storm的三点,一条都不满足的实时场景,即,不要求纯实时,不要求强大可靠的事务机制,不要求动态调整并行度,那么可以考虑使用Spark Streaming
2、考虑使用Spark Streaming最主要的一个因素,应该是针对整个项目进行宏观的考虑,即,如果一个项目除了实时计算之外,还包括了离线批处理、交互式查询等业务功能,而且实时计算中,可能还会牵扯到高延迟批处理、交互式查询等功能,那么就应该首选Spark生态,用Spark Core开发离线批处理,用Spark SQL开发交互式查询,用Spark Streaming开发实时计算,三者可以无缝整合,给系统提供非常高的可扩展性。
35.storm核心?
storm被设计成用一种容错的水平扩展方法处理大量数据。它是一个具有最高摄取率的流式数据框架。虽然Storm是无状态的,但它通过ApacheZooKeeper管理分布式环境和集群状态。它很简单,可以对实时数据并行执行各种操作。
ApacheStorm仍然是实时数据分析领域的领导者。Storm易于设置、操作,并确保至少通过TopDlogy处理每个消息(消息至少被消费过一次)。
Storm vs Hadoop
基本上,Hadoop和Storm框架用于分析大数据。两者相辅相成,在某些方面有所不同。ApacheStorm的所有操作都是持久性的,而Hadoop在所有方面都很好,但在实时计算方面却落后。

      1. tuple  :元组

      数据结构,有序的元素列表。通常是任意类型的数据,使用,号分割,交给storm计算。

      2. stream  :一系列tuple.

      3. Spouts  :水龙头。数据源。

      4. Bolts  :转接头。逻辑处理单元,spout数据传给bolt, bolt处理后产生新的数据,可以filter、 聚合、分组。

      接收数据-处理; ->输出给bolt (多个)。

       5. Topology  :不会停止的,除非手动杀死。MR是会停止的。

       6. tasks  : spout和bolt的执行过程就是task.

      spout和bolt都可以以多实例方式运行(在单独的线程中)

      7. workers  :工作节点。storm在worker之 间均衡分发任务。监听job,启动或者停止进程。

8.stream grouping  :控制tuple如何进行路由。

      内置4个分组策略.

36.sparkStreagming核心抽象?
Dstream
37.什么是Dstream?
Dstream:sparkstreaming的数据抽象,代表一个持续不断的数据流。
由一个个连续的batch组成,所谓batch就是一个时间片的rdd。
所以我们在写sparkstreaming程序的时候,一定设置一个时间间隔,inXXX
首先我们的sparkStreaming程序操作的Dstream ,那么 地位和spark core 里面的rdd的地位一样,它也有转换和output操作。也会由一个Dstream 经过算子转变为另外一个Dstream。
在我们画的图中,Dstream01–》Dstream02 ,其实 就是把Dstream里面的每一个时间段的RDD,都执行了一遍了这个Map,生成了一个新的RDD,新的RDD又组成了Dstream02.
在这里插入图片描述
其实Dstream 就是spark 对rdd进行的一个封装,底层走到流程还是我们之前讲的RDD的那一套。
38.sparkStreagming 对象调用stop 方法,参数解释?
调用stop方法,不仅仅会停止掉streamingContext ,而且可以把sparkContext也会停掉,
如果你想 后续能够继续使用 sparkContext ,Stop方法里面可以写false
一个JVM里面只能同时有一个streamingContext ,那么如果用多个stop(false),再创建一个
39.sparkStreagming 特有算子?

算子 描述
updateStateByKey(func) 返回一个新状态的DStream,其中每个键的状态是根据键的前一个状态和键的新值应用给定函数func后的更新。这个方法可以被用来维持每个键的任何状态数据。
transform(func) 通过对源DStream的每RDD应用RDD-to-RDD函数返回一个新的DStream,这可以用来在DStream做任意RDD操作。
window(windowLength, slideInterval) 返回一个基于源DStream的窗口批次计算后得到新的DStream。

40.sparkStreagming 数据源?
hdfs、flume、kafka,主要是最后一个
41.sparkStreagming receiver?
setMaster(“local[1]”) ,就没有了打印的结果
因为注意在spark streaming在默认情况下,接受数据的时候,内部需要有一个叫receiver的东西,它是专门得占用一个线程来不断的接受数据。

注意: 创建一个dstream的流,就需要一个receiver ,我们可以针对不同的数据源创建不同的流,可以创建多个流,这些流对应的Dstream当然可以执行我们的算子操作。

小结:我们在设置masterF的时候,这个core 的个数一定要大于等于 流的个数 + 1

我们的sparkStreaming 通过我们的wordCount程序, 得到是一个batch 的数据
42.sparkStreagming 持久化 容错?
与sparkcore一致
43.程序挂掉分为?
executor或者driver
44.driver 挂了,如何解决?
这个现象是比executor严重的,整个程序只有一个driver。
解决:2种
1.checkpoint,可以对数据进行checkpoint,可以从中恢复出来
2.–supervise,实在spark submit,集群模式下的参数,不需要加什么参数
但是如果driver挂掉,会自动重启
45.可以对那些数据 checkPoint?
元数据,中间结果数据
46.容错的语义?
至多一个、至少一个、有且只有一个
47.应用程序如何升级?
一种是停掉,然后部署升级。老的停掉,上新的。
现在是在线更新,不停机,老的新的同时计算同一数据,直到两个计算的数据完全同步,再把老的停掉,自然而然地使用新的
48.sparkStreagming 消费kafka 的数据方式?
1.receiver方式:高阶API 采用的是zk保存offset
2.direct方式:低阶API,是自己管理偏移量,是可靠的,能保证有且仅有一次的语义
49.receiver方式 如何保证消息的可靠性?
预写入
50.direct 方式 自己如何保存偏移量?
通过zk ck
51.即席查询
用户根据需求 自定义查询
52.hive 和 spark sql 有什么区别?
底层不一样,
hive是mr
spark sql 是spark
53.spark sql特点?
1.易整合
2.统一的数据访问方式
3.兼容hive
4.标准的数据连接
54.spark1.0与2.0的差别?
spark2.0中sparksession,统一了sqlcontext和hivecontext
55.spark sql的核心抽象?
dataFrame
56.Rdd 和 dataFrame 的区别?
从根本上来说,dataframe具有数据结构,Rdd没有。
RDD是分布式的Java对象的集合。DataFrame是分布式的Row对象的集合。DataFrame除了提供了比RDD更丰富的算子以外,更重要的特点是提升执行效率、减少数据读取以及执行计划的优化,比如filter下推、裁剪等。

提升执行效率

RDD API是函数式的,强调不变性,在大部分场景下倾向于创建新对象而不是修改老对象。这一特点虽然带来了干净整洁的API,却也使得Spark应用程序在运行期倾向于创建大量临时对象,对GC造成压力。在现有RDD API的基础之上,我们固然可以利用mapPartitions方法来重载RDD单个分片内的数据创建方式,用复用可变对象的方式来减小对象分配和GC的开销,但这牺牲了代码的可读性,而且要求开发者对Spark运行时机制有一定的了解,门槛较高。另一方面,Spark SQL在框架内部已经在各种可能的情况下尽量重用对象,这样做虽然在内部会打破了不变性,但在将数据返回给用户时,还会重新转为不可变数据。利用 DataFrame API进行开发,可以免费地享受到这些优化效果。

减少数据读取

分析大数据,最快的方法就是 ——忽略它。这里的“忽略”并不是熟视无睹,而是根据查询条件进行恰当的剪枝。
上文讨论分区表时提到的分区剪 枝便是其中一种——当查询的过滤条件中涉及到分区列时,我们可以根据查询条件剪掉肯定不包含目标数据的分区目录,从而减少IO。
对于一些“智能”数据格 式,Spark SQL还可以根据数据文件中附带的统计信息来进行剪枝。简单来说,在这类数据格式中,数据是分段保存的,每段数据都带有最大值、最小值、null值数量等 一些基本的统计信息。当统计信息表名某一数据段肯定不包括符合查询条件的目标数据时,该数据段就可以直接跳过(例如某整数列a某段的最大值为100,而查询条件要求a > 200)。
此外,Spark SQL也可以充分利用RCFile、ORC、Parquet等列式存储格式的优势,仅扫描查询真正涉及的列,忽略其余列的数据。

执行优化

在这里插入图片描述

为了说明查询优化,我们来看上图展示的人口数据分析的示例。图中构造了两个DataFrame,将它们join之后又做了一次filter操作。如果原封不动地执行这个执行计划,最终的执行效率是不高的。因为join是一个代价较大的操作,也可能会产生一个较大的数据集。如果我们能将filter下推到 join下方,先对DataFrame进行过滤,再join过滤后的较小的结果集,便可以有效缩短执行时间。而Spark SQL的查询优化器正是这样做的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。
得到的优化执行计划在转换成物 理执行计划的过程中,还可以根据具体的数据源的特性将过滤条件下推至数据源内。最右侧的物理执行计划中Filter之所以消失不见,就是因为溶入了用于执行最终的读取操作的表扫描节点内。
对于普通开发者而言,查询优化 器的意义在于,即便是经验并不丰富的程序员写出的次优的查询,也可以被尽量转换为高效的形式予以执行。
57.spark on hive与hive on spark: spark的区别
spark on hive: hive作为数据源使用spark计算
hive on spark: spark作为计算引擎,使用hive计算
58.Rdd 创建DF 的两种方式?
笼统的来说,元数据,反射
都是toDF,反射是创建一个样例类,将Rdd转换成df
59.开窗函数?

函数 意义
OVER(): 指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变而变化。
CURRENT ROW: 当前行
n PRECEDING: 往前n行数据
n FOLLOWING: 往后n行数据
UNBOUNDED: 起点,UNBOUNDED PRECEDING 表示从前面的起点, UNBOUNDED FOLLOWING表示到后面的终点
LAG(col,n,default_val): 往前第n行数据
LEAD(col,n, default_val): 往后第n行数据
NTILE(n): 把有序分区中的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型。

60.hive 自定义函数?

hive自定义函数:
(1)UDF(User-Defined-Function)
一进一出
(2)UDAF(User-Defined Aggregation Function)
聚集函数,多进一出
类似于:count/max/min
(3)UDTF(User-Defined Table-Generating Functions)
一进多出
如lateral view explore()
此文章由小白,毕姥爷和赵帅帅三人倾力打造。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章