Spark总结
#####1. 翻译
Apache Spark是一个快速的、通用的集群计算系统。它提供Java、Scala、Python和R中的高级api,以及一个支持通用执行图的优化引擎。它还支持一组丰富的高级工具,包括用于SQL和结构化数据处理的[Spark SQL]、用于机器学习的[MLlib]、用于图形处理的[GraphX]和用于流媒体的[Spark streams]。
#####2.什么是spark?
> Spark是一种基于内存的快速、通用、可扩展的大数据分析引擎。
##### 3.spark生态:
> + spark core: spark 的核心计算
> + spark sql (结构化数据):对历史数据做交互式查询(即席查询:用户根据自己的需求 自定义查询)
> + spark Streaming : 近实时计算
> + spark mlib :机器学习
> + spark graphX:图计算(关注事物本身而且关注事物之间的联系)
##### 4. 什么是结构化和非结构化?
结构化数据——能够用数据或统一的结构加以表示,如数字、文字、符号。结构化数据也称作行数据,是由二维表结构来逻辑表达和实现的数据,严格地遵循数据格式与长度规范,主要通过关系型数据库进行存储和管理。
半结构化数据——是介于完全结构化数据(如关系型数据库、面向对象数据库中的数据)和完全无结构的数据(如声音、图像文件等)之间的数据,XML、HTML文档就属于半结构化数据。它一般是自描述的,数据的结构和内容混在一起,没有明显的区分。
非结构化数据——非结构化数据是数据结构不规则或不完整,没有预定义的数据模型,不方便用数据库二维逻辑表来表现的数据。包括图像和音频/视频信息等等。丢失的视频数据就属于非结构化数据。
#####5.实时计算框架Storm sparkString flink 区别?
> Storm ,flink 实时处理性好 一条一条处理
> sparkString 可以批处理 非实时
> flink 批处理 flink SQL
##### 6.spark 的资源调度? standalone yarn mesos
> local模式:利用多线程对程序进行单机调试;分布式部署模式:
> (1)、standalone:独立模式,类似于MapReduce1;
> (2)、spark on yarn:利用yarn进行统一的资源调度和管理;
> (3)、spark on mesos:类似于yarn进行资源调度,支持粗粒度和细粒度的调度,前者节约资源调度时间,后者减少资源的浪费;
##### 7.什么是Rdd?
> 弹性分布式数据集
> 这三个特性分别为:分区,不可变,并行操作。
#####8.spark on hive 和 hive on spark?
> + spark on hive:hive 作为数据源,spark计算
> + hive on spark:spark 作为hive 底层的计算引擎
##### 9.spark 为什么比hadoop 的mr快?
> 1. 基于内存
> 2. spark实现了DAG引擎
> 3. spark的容错
1.消除了冗余的HDFS读写
Hadoop每次shuffle操作后,必须写到磁盘,而Spark在shuffle后不一定落盘,可以cache到内存中,以便迭代时使用。如果操作复杂,很多的shufle操作,那么Hadoop的读写IO时间会大大增加。
2.消除了冗余的MapReduce阶段
Hadoop的shuffle操作一定连着完整的MapReduce操作,冗余繁琐。而Spark基于RDD提供了丰富的算子操作,且reduce操作产生shuffle数据,可以缓存在内存中。
3.JVM的优化
Spark Task的启动时间快。Spark采用fork线程的方式,Spark每次MapReduce操作是基于线程的,只在启动。而Hadoop采用创建新的进程的方式,启动一个Task便会启动一次JVM。
Spark的Executor是启动一次JVM,内存的Task操作是在线程池内线程复用的。
##### 10.什么是DAG? 有向无环图
##### 11.spark 的特点?
1)快:与 Hadoop的 Mapreduce/相比, Spark基于内存的运算要快100倍以上,基于硬盘的运算也要快10上。Spak实现了高效的DAG执行引擎,可以通过基于内存来高效处理数据流。计算的中间结果是存在于内存中
2)易用:spaは支持ava、 Python和a的AP,还支持超过so种高级算法,使用户可以快速构建不同的应用。而且Spak支持交互式的 Python和 Scala的 Shell,,可以非常方便地在这些Shel中使用 Spark集群来验证解决问题的方法。
3)通用: Spark提供了统一的解决方案。 Spark可以用于批处理、交互式查询( Spark SQL)、实时流处理( Spark Streaming)、机器学习( Spark Mllib)和图计算( Graphx)。这些不同类型的处理都可以在同一个应用中无缝使用。减少了开发和维护的人力成本和部署平台的物力成本
4)兼容性: Spark可以非常方便地与其他的开源产品进行融合。比如, Spark可以使用 Hadoop的YARN和Apache Mesos作为它的资源管理和调度器,并且可以处理所有 Hadoop支持的数据,包括HDFS、 Hbase等。这对于已经部署 Hadoop集群的用户特别重要,因为不需要做任何数据迁移就可以使用Spak的强大处理能力。
##### 12.spark 能代替hadoop 吗?
不能,仅仅是mr的替代方案
##### 13.spark Rdd 的缓存?
cache / persist 内存中缓存 ,内部的优化机制。当Rdd 重复被使用了,不需要在重新计算,直接从内存中获取使用
1、提高性能 (默认将数据缓存在内存中)
2.缓存使用的函数cache,Persist,**标识RDD可以被缓存**
cache函数底层调用Persist
storage level:标识缓存的位置
MEMORY_ONLY 存在内存中的
MEMORY_ONLY_2 存在内存中的
MEMORY_ONLY_SER 存在内存中,以序列化形式存储
DISK_ONLY 存储在磁盘中
##### 14.driver 的作用?
Spark 的驱动器是执行开发程序中的 main 方法的进程。它负责开发人员编写的用来创 建 SparkContext、创建 RDD,以及进行 RDD 的转化操作和行动操作代码的执行。
启动 Spark shell 的时候,系统后台自启了一个 Spark 驱动器程序, 就是在 Spark shell 中预加载的一个叫作 sc 的 SparkContext 对象。如果驱动器程序终止,那 么 Spark 应用也就结束了。
主要负责:
1)把用户程序转为作业(JOB)
2)跟踪 Executor 的运行状况
3)为执行器节点调度任务
4)UI 展示应用运行状况
##### 15.excutor 的作用?
Spark Executor 是一个工作进程,负责在 Spark 作业中运行任务,任务间相互独立。Spark 应用启动时,Executor 节点被同时启动,并且始终伴随着整个 Spark 应用的生命周期而存 在。如果有 Executor 节点发生了故障或崩溃,Spark 应用也可以继续执行,会将出错节点上 的任务调度到其他 Executor 节点上继续运行。
主要负责:
1)负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程;
2)通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存式 存储。RDD 是直接缓存在 Executor 进程内的,因此任务可以在运行时充分利用缓存数据加速运算。
##### 16.spark spark-submit脚本的参数有哪些?
参数名 参数说明
--master master 的地址,提交任务到哪里执行,例如 spark://host:port, yarn, local
--deploy-mode 在本地 (client) 启动 driver 或在 cluster 上启动,默认是 client
--class 应用程序的主类,仅针对 java 或 scala 应用
--name 应用程序的名称
--jars 用逗号分隔的本地 jar 包,设置后,这些 jar 将包含在 driver 和 executor 的 classpath 下
--packages 包含在driver 和executor 的 classpath 中的 jar 的 maven 坐标
--exclude-packages 为了避免冲突 而指定不包含的 package
--repositories 远程 repository
--conf PROP=VALUE
指定 spark 配置属性的值,
例如 -conf spark.executor.extraJavaOptions="-XX:MaxPermSize=256m"
--properties-file 加载的配置文件,默认为 conf/spark-defaults.conf
--driver-memory Driver内存,默认 1G
--driver-java-options 传给 driver 的额外的 Java 选项
--driver-library-path 传给 driver 的额外的库路径
--driver-class-path 传给 driver 的额外的类路径
--driver-cores Driver 的核数,默认是1。在 yarn 或者 standalone 下使用
--executor-memory 每个 executor 的内存,默认是1G
--total-executor-cores 所有 executor 总共的核数。仅仅在 mesos 或者 standalone 下使用
--num-executors 启动的 executor 数量。默认为2。在 yarn 下使用
--executor-core 每个 executor 的核数。在yarn或者standalone下使用
##### 17.spark 配置的方式?
> 代码 脚本 配置文件 , 优先级一次降低的
##### 18.spark的资源调度方式?
> local模式
> standalone
> on-yarn : yarn-cluster yarn-client
> mesos (粗细粒度)
##### 19.spark 的提交方式有两种?
> 发布模式: client cluster
##### 20.spark collect 算子的作用?
> 收集一个弹性分布式数据集的所有元素到一个数组中,从远程集群拉取数据到driver端。
##### 21.oom?
> OOM - Out of Memory,内存溢出
##### 22.spark yarn 的提交方式有:
> --master yarn-client :资源调度yarn 提交方式 client
> --master yarn-cluster :资源调度yarn 提交方式 cluster
cluster 模式会在集群的某个节点上为 Spark 程序启动一个称为 Master 的进程,然后 Driver 程序会运行正在这个 Master 进程内部,由这种进程来启动 Driver 程序,客户端完成提交的步骤后就可以退出,不需要等待 Spark 程序运行结束,这是四一职中适合生产环境的运行方式
client 模式也有一个 Master 进程,但是 Driver 程序不会运行在这个 Master 进程内部,而是运行在本地,只是通过 Master 来申请资源,直到运行结束,这种模式非常适合需要交互的计算。显然 Driver 在 client 模式下会对本地资源造成一定的压力。
##### 23.spark client 和 cluster 提交方式的区别?
> 主要区别在于:Driver程序的运行节点
> + client:driver 在你当前提交的节点运行,driver 需要频繁的和集群通信占用大量的网络带宽,容易挂掉,好处是方便查看日志便于调试,多用于学习和测试
> + cluster :driver 在集群的节点。挂掉会自动重启
##### 24.Rdd的属性?
> 1. RDD是一个分片的数据集合;
> 2. RDD的函数针对每个分片进行计算,并行计算
> 3. rdd之间的依赖关系(宽窄依赖)
> 4. key-value型RDD是根据哈希来分区的
##### 25.Rdd的并行度?
> 一个Rdd 可以有多个分片,一个分片对应一个task,分片的个数决定并行度
> 并行度并不是越高越好, 还要考虑资源的情况
> 当并行度过低时,Spark集群会出现资源闲置的情况。
> 当并行度过高时,每个分区产生的间接开销累计起来会更大。
##### 26.spark的算子有两种?
> 转换算子:transformation 由一个Rdd 生成一个新的Rdd 不会被立即执行,记录的都是一系列的操作
>
> 动作算子:action 立即执行,返回都是结果
##### 27.spark的容错?
> lineage:血缘关系,根据血缘关系从新计算 进行容错
> checkpoint:设置检查点,一般都是文件系统,磁盘io
##### 28.spark的持久化?
> cache 、persist、checkpoint
> cache持久化:
> cache实际上是persist的一种简化方式,是一种**懒执行**的,执行action类算子才会触发,cahce后返回值要赋值给一个变量,下一个job直接基于变量进行操作。
> checkpoint:会将RDD的数据存储到HDFS中,安全系数较高,因为HDFS会有备份
##### 29.什么是宽依赖,窄依赖?
> 窄依赖:父RDD与子RDD,partition之前的关系是一对一(或者多对一)的;
> 宽依赖:父RDD与子RDD,partition之前的关系是一对多的;
> 作用:切割job,划分stage;Application,一个算子或者多个算子拆分(宽依赖和窄依赖)
> - l 是碰到一个宽依赖,就会切割一个stage
> - l rdd里面存储的不是数据,而一个代码逻辑
> - l 数据只有在stage与stage之间的时候将数据落地,到底数据写到哪个小文件(Reduce输出)中,是由分区器决定的.
> - l stage里面的并行度是由最后一个rdd来决定的.
##### 31.创建Rdd的方式有几种?
> 1. 使用程序中的集合创建RDD val arr = Array() sc.parallelize(arr) sc.makeRDD(arr )
> 2. 使用本地文件创建RDD
> 3. 使用HDFS文件创建RDD
##### 32.map和mapPartitons有什么区别?
> map是对rdd中的每一个元素进行操作
> mapPartitions则是对rdd中的每个分区的迭代器进行操作
##### 30.总结Rdd 的算子(30个以上)
| **算子** | **类型** | **说明** |
| ----------------------------- | ------------ | ------------------------------------------------------------ |
| **++** | | 合并两个RDD |
| **aggregate** | 执行算子 | 根据初始化值进行对rdd种的元素进行聚合,结束之后每个分区会有一个结果,后面会根据这个分区结果再进行一次聚合 |
| **aggregateByKey** | 执行算子 | 和aggregate类似,但是操作的是RDD是Pair类型 |
| **cache()** | 控制操作 | 当第一次遇到Action算子时才会触发持久化操作。Cache()是persis的一种特殊情况,将RDD持久化到内存中 |
| 转换算子 | | |
| **Cartesian** | 转换算子 | 计算两个RDD之间的笛卡尔乘积,并将它们作为新的RDD返回 |
| **coalesce** | 转换算子 | 将RDD进行重分区,使用HashPartitioner。它的简版是repartition算子 |
| **Cogroup** | 转换算子 | 相当于SQL中的**全外关联**full outer join,返回左右RDD中的记录,关联不上的为空。 |
| **Collect** | **执行算子** | 一个RDD转换成数组。根据一个偏函数返回一个符合偏函数的结果集RDD。即将RDD转换成数组 |
| **collectAsMap** | | 一个RDD转换成Map |
| **combineByKey** | 转换算子 | **将RDD[K,V]转换成RDD[K,C],**这里的V类型和C类型可以相同也可以不同。(单个值类型v操作 , 同分区内合并操作 , 多分区间的合并操作 ) |
| **Count** | **执行算子** | 返回RDD中的元素数量 |
| **countByKey** | **执行算子** | 统计RDD[K,V]中每个K的数量。 |
| **createCombiner** | 参数 | 组合器函数,用于将V类型转换成C类型,输入参数未RDD[K,V]中的V,输出为C |
| **distinct** | 转换算子 | 去除RDD重复的元素,返回所有元素不重复的RDD |
| **flatMap** | 转换算子 | 类似于map。1对多,可以理解成将原来的数据集拍扁了。RDD中每个元素可生成一个或多个元素构成的新RDD例如将数组、列表拆分成单个值或字符串拆分成单个字符 |
| **flatMapValues** | 转换算子 | 类似于flatMap,只不过flatMapValues是针对[K,V]中的V值进行flatMap操作。 |
| **filter** | 转换算子 | 过滤,根据里面的规则返回(true的)一个过滤过后的rdd |
| **First** | **执行算子** | 返回RDD中的第一个元素,不排序。 |
| **Fold** | 执行算子 | 是aggregate的简化版,将aggregate中的seqOp和combOp使用同一个函数op。 |
| **foldByKey** | 转换算子 | 作用于RDD[K,V],根据K将V做折叠、合并处理。 |
| **foreach** | **执行算子** | 遍历RDD,将函数f应用于每一个元素。需要注意如果RDD执行foreach,只会在Executor端有效,并且不是Driver端 |
| **foreachPartition** | **执行算子** | 与foreach类似,只不过是对每一个分区使用f |
| **fullOuterJoin** | 转换算子 | 类似于SQL的全连接, |
| **glom** | 转换算子 | 将RDD中每一个分区中所有类型为T的元素转换成Array[T] |
| **groupBy** | | 根据业务需求,按照自定义的返回值来分区 |
| **groupByKey** | 转换算子 | 根据key将value进行分组。该函数用于将RDD[K,V]中每个K对应的V值,合并到一个集合Iterable[V]中 |
| **intersection** | 转换算子 | 取交集。返回两个RDD中相同的数据集合,返回元素去重。类似于SQL中的inner join |
| **join** | 转换算子 | 类似于SQL中的内关联join,只返回两个RDD根据K可以关联上的结果 |
| **leftOuterJoin** | 转换算子 | 类似于SQL中的左外关联left outer join,返回结果以前面的RDD为主,关联不上的记录为空 |
| **Lookup** | 执行算子 | 用于(K,V)类型的RDD,指定K值,返回RDD中该K对应的所有V值。 |
| **map** | 转换算子 | 对RDD中的每一个元素经过func函数转换后形成一个新的RDD |
| **mapPartitions** | 转换算子 | 是map的一个变种。mapPartitions的输入函数是应用于每个分区,也就是把每个分区中的内容作为整体来处理的。**在映射过程中频繁创建额外的对象时mapPartitions比map高效,例如在RDD中创建数据库的连接等。** |
| **mapPartitionsWithIndex** | 转换算子 | 函数作用同mapPartitions,不过提供了两个参数,第一个参数为分区的索引。 |
| **mapValues** | 转换算子 | 类似于map算子,只不过此算子针对[K,V]值中的V值进行map。进行输入函数应用于RDD中Kev-Value的Value,原RDD中的Key保持不变与新的Value一起组成新的RDD中的元素。如(panda,0)转成(panda,(0,1)) |
| mergeValue | 参数 | 合并值函数,将一个C类型和V类型值合并成一个C类型,输入参数为(C,V),输出为C |
| mergeCombiners | 参数 | 合并组合器函数,用于将两个C类型值合并成一个C类型,输入参数为(C,C),输出为C |
| numPartition | 参数 | 结果RDD分区数,默认保持原有分区数 |
| **partitionBy** | 转换算子 | 该函数根据partitioner函数生成新的ShuffleRDD,将原RDD重新分区。 |
| persist() | 控制操作 | persis(level:StorageLevel)可以传入缓存级别,默认是MEMORY_ONLY,此时同cache()操作 |
| 转换算子 | | |
| **randomSplit** | 转换算子 | 该函数根据weights权重,将一个RDD切分成多个RDD。 |
| **Reduce** | 执行算子 | 将RDD中元素两两传递给输入函数,同时产生一个新的值。根据映射函数,对RDD中的元素进行二元计算 |
| **reduceByKey** | 转换算子 | 对元素为RDD[K,V]对的RDD中Key相同的元素的Value进行reduce |
| **reduceByKeyLocally** | 转换算子 | 和reduceByKey类似。RDD[K,V]中每个K对应的V值根据映射函数来运算,运算结果映射到一个Map[K,V]中,而不是RDD[K,V]。 |
| **repartition** | 转换算子 | 该函数其实就是coalesce函数第二个参数为true的实现 |
| **rightOuterJoin** | 转换算子 | 类似于SQL中的右外关联right outer join,返回结果以参数中的RDD为主,关联不上的记录为空 |
| **saveAsHadoopFile** | 存储操作 | 将RDD存储在HDFS上的文件中,支持老版本Hadoop API。可以指定outputKeyClass、outputValueClass以及压缩格式 |
| **执行算子** | | |
| **saveAsHadoopDataset** | 存储操作 | 可以用于将RDD保存到除了HDFS的其他存储中,比如HBase。在JobConf中通常需要关注或设置5个参数:文件保存路径、Key值的class类型、value值的class类型、RDD的输出格式(OutputFormat)以及压缩相关的参数 |
| **执行算子** | | |
| **saveAsNewAPIHadoopFile** | 存储操作 | 用于将RDD数据保存到HDFS上,使用新版本的Hadoop API,用法基本同**saveAsHadoopFile** |
| **执行算子** | | |
| **saveAsNewAPIHadoopDataset** | 存储操作 | 该方法作用同**saveAsHadoopDataset,**只不过采用新版本的Hadoop API |
| **执行算子** | | |
| **saveAsObjectFile** | 存储操作 | 将RDD中的元素序列化成对象,存储到文件中。对于HDFS,默认采用SequenceFile保存 |
| **执行算子** | | |
| **saveAsSequenceFile** | 存储操作 | 将RDD以SequenceFile的文件格式**保存到HDFS**上。 |
| **执行算子** | | |
| **saveAsTextFile** | 存储操作 | 将RDD以文本文件的格式存储到文件系统中。 |
| **执行算子** | | |
| **sortBy** | **执行算子** | 排序。根据规则来定义排序。true升序 false,升序 |
| **sortByKey** | **执行算子** | 排序。根据按value的排序 |
| **subtract** | 转换算子 | 该函数类似于intersection,但返回在RDD中出现,并且不在otherRDD中出现的元素,不去重。 |
| **subtractByKey** | 转换算子 | 和基本转换操作中的subtract类似。只不过这里是针对K的,返回在主RDD中出现,并且不在otherRDD中出现的元素。 |
| **take(n)** | 执行算子 | 用于获取RDD中从0到n-1下标的元素,不排序。 |
| **takeOrdered** | 执行算子 | 和top类似,只不过以和top相反的顺序返回元素 |
| **top** | 执行算子 | 从RDD中,按照默认(降序)或者指定的排序规则,返回前num个元素 |
| **union** | 转换算子 | 就是将两个RDD进行合并(类型需一致),返回两个RDD的并集,不去重。 |
| **zip** | 转换算子 | 将两个RDD组合成Key/Value形式的RDD。这里默认两个RDD的partitio数以及元素数量都相同,否则会抛出异常 |
| **zipPartitions** | 转换算子 | 将多个RDD按照partition组合成为新的RDD,该操作需要组合的RDD具有相同的分区数,但对于每个分区内的元素数量没有要求 |
| **zipWithIndex** | 转换算子 | 将RDD中的元素和这个元素在RDD中的ID(索引号)组合成键/值对。 |
| **zipWithUniqueId** | 转换算子 | 将RDD中元素和一个唯一ID组合成键/值对。该唯一ID生成的算法如下:①每个分区中第一个元素的唯一ID值为该分区索引号;②每个分区中第N个元素的唯一ID值为(前一个元素唯一ID-ID值)+(该RDD总的分区数) |
##### 33.Spark的重分区算子? 区别?
> coalesce 缩减分区数,用于大数据集过滤后,提高小数据集的执行效率
>
> repartition 根据分区数,重新通过网络随机洗牌所有数据
>
> 区别:
>
> - coalesce: 重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定
> - repartition: 实际上是调用的coalesce,默认是进行shuffle的
##### 34.总结宽依赖算子?
> 1. 所有bykey 的算子 groupbykey reducebyky…
> 2. repatition,cartesian算子
> 3. 部分join算子
##### 35.使用什么方法可以代替join?
> 广播变量+map+filter
##### 36.reducebykey和groupbykey的区别?
> 1. reduceByKey:按照 key 进行聚合,在 shuffle 之前有 combine(预聚合)操作,返回结果 是 RDD[k,v].
> 2. groupByKey:按照 key 进行分组,直接进行 shuffle。
> 3. 开发指导:reduceByKey 比 groupByKey,建议使用。但是需要注意是否会影响业务逻辑
##### 37.DAG如果划分stage?
(底层对应了三个RDD:MapPartitionsRDD、ShuffleRDD、MapPartitionsRDD)
>1、使用出发job的最后一个RDD,创建finalStage(创建一个stage对象,并且将stage加入到DAGScheduler内部的内存缓存中)
>
>2、使用finalStage创建一个job(这个job的最后一个stage,就是 finalStage)
>
>3、将job加入到内存缓存中
>
>4、使用 submitStage() 提交 finalStage
>提交stage的方法(stage划分算法入口):
>
>调用 getMissingParentStage() 获取当前这个 stage 的父 stage:
>
> 往栈中推入stage的最后一个RDD
>
> while循环对stage的最后一个RDD,调用自己定义的visit()方法
>
> visit():如果是窄依赖,将RDD放入栈中,如果是宽依赖,使用宽依赖的那个RDD创建一个stage,将isShuffleMap设为true
>
>提交stage,为stage创建一批task,task数量与Partition数量相同
>
>计算每个task对应的Partition的最佳位置(就是从stage最后一个RDD开始,去找被cache或checkpoint的RDD的Partition,task的最佳位置,就是该Partition的位置,这样task就在那个节点上执行,不需要计算之前的RDD;如果从最后一个RDD到最开始的RDD,都没有被cache或checkpoint,那么最佳位置就是Nil,就是没有最佳位置)
>
>5.、针对stage的task,创建TaskSet对象,调用TaskScheduler的submitTask方法,提交TaskSet,提交到Excutor上去执行
##### 38.如何划分job?
> 1)Application:初始化一个SparkContext即生成一个Application;
>
> 2)Job:一个Action算子就会生成一个Job;
>
> 3)Stage:根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage;
>
> ==对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据.==
>
> 4)Task:Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行即为一个Task。
>
> 注意:Application->Job->Stage-> Task 每一层都是 1 对 n 的关系。
##### 39.spark的提交流程总结?
1、Driver端会调用SparkSubmit类(内部执行submit->doRunMain->通过反射
获取应用程序的主类对象->执行主类的main方法)
2、构建sparkConf和sparkContext对象,在sparkContext入口做了三件事,创建
了sparkEnv对象(创建了ActorSystem对象)TaskScheduler(用来生成并发送
task给Executor)DAGScheduler(用来划分Stage)
3、clientActor 将任务封装到 ApplicationDescription 对象并且提交给Master
4、Master收到任务信息后,将任务信息存到内存中,同时放到队列中(waitingApp)
5、任务信息开始执行后,调用schedule方法,进行资源的调度。
6、将调度好的资源封装到LaunchExecutor并发送给对应的worker。
7、worker 接收到 master 发送来的任务调度信息(LaunchExecutor),将信息封装
成一个ExecutorRunner对象。
8、封装成ExecutorRunner后,调用ExecutorRunner的·start方法,开始启动GoarseGrainedExecutorBackend对象
9、Executor启动后DriverActor进行反向注册。
10、与DriverActor注册成功后,创建一个线程池(TreadPool)用来执行任务
11、当所有的Executor注册完成后,意味着作业环境准备好了,Driver端会结束与
sparkContext 对象的初始化。
12、当Driver初始化完成后(创建一个sc实例)会继续执行我们自己提交的App
代码,当触发了action算子时就会触发一个job,这时就会调用DAGScheduler对象
进行Stage划分。
13、DagScheduler开始进行stage划分。
14、将划分好的stage按照分区生成一个一个的task,并且封装到TaskSet对象中
然后TaskSet提交到TaskScheduler
15、TaskScheduler按照提交过来的TaskSet,拿到一个序列化器,将TaskSet序列化
,将序列化好的Task封装到LaunchExecutor并且提交到DriverActor。
16、DriverActor把LauchExcutor发送到Excutro上。
17、Executor接收到DriverActor发送过来的任务(LaunchExecutro),会将其封装成为
TaskRunner,然后从线程池中获取线程来执行TaskRunner。
18、TaskRunner拿到反序列化器,反序列Taskset,然后执行App代码,也就是对
RDD分区上执行的算子和自定义函数。
ClientActor:负责和Master通信,向Master注册任务信息
DriverActor:负责和Executor进行通信,接收到 Executor反向注册和把任务发送到Executer。
##### 40.spark 持久化的级别?
| StorageLevel | 说明 |
| :------------------ | :----------------------------------------------------------- |
| NONE | 没有一个 |
| MEMORY_ONLY | 使用未序列化的Java对象格式,将数据保存在内存中。如果内存不够存放所有的数据,则数据可能就不会进行持久化,默认的持久化策略 |
| MEMORY_AND_DISK | 使用未序列化的Java对象格式,优先尝试将数据保存在内存中。如果内存不够存放所有的数据,会将数据写入磁盘文件中。不会立刻输出到磁盘 |
| MEMORY_ONLY_SER | RDD的每个partition会被序列化成一个字节数组,节省空间,读取时间更占CPU |
| MEMORY_AND_DISK_SER | 序列化存储,超出部分写入磁盘文件中 |
| DISK_ONLY | 使用未序列化的Java对象格式,将数据全部写入磁盘文件中 |
| MEMORY_ONLY_2 | 对于上述任意一种持久化策略,如果加上后缀_2,代表的是将每个持久化的数据,都复制一份副本,并将副本保存到其他节点上 |
| OFF_HEAP | RDD序列化存储在Tachyon 优点:减少频繁的GC,减少内存的使用,提高程序性能。 缺点:没有数据备份,也不能像alluxio那样保证数据高可用,丢失数据则需要重新计算。 |
##### 41.spark 持久化的选择?
> 如果默认能满足使用默认的
> 如果不能于MEMORY_ONLY很好的契合,建议使用MEMORY_ONLY_SER
> 尽可能不要存储数据到磁盘上,除非数据集函数计算量特别大,或者它过滤了大量数据,否则从新计算一个分区的速度和从磁盘中读取差不多
> 如果想拥有快速故障恢复能力,可以使用复制存储级别(_2)
> 可以自定义存储级别(如复制因子为3),使用StorageLevel单例对象apply方法
##### 42.spark 持久化和容错的应用场景?
> 1, checkpoint(容错)是考虑安全性,
> RDD 可以使用 persist() 方法或 cache() 方法进行持久化。数据将会在第一次 action 操作时进行计算,并缓存在节点的内存中。Spark 的缓存具有容错机制,如果一个缓存的 RDD 的某个分区丢失了,Spark 将按照原来的计算过程,自动重新计算并进行缓存。
> 2,持久化是为高效性
> 持久化到磁盘、已序列化的 Java 对象形式持久化到内存(可以节省空间)、跨节点间复制、以 off-heap 的方式存储在Tachyon 。
> 这些存储级别通过传递一个 StorageLevel 对象(Scala、Java、Python)给 persist() 方法进行设置。cache() 方法是使用默认存储级别的快捷设置方法,默认的存储级别是 StorageLevel.MEMORY_ONLY(将反序列化的对象存储到内存中)。
#####43. spark的序列化? java kryo
Java序列化
在默认情况下,Spark采用Java的ObjectOutputStream序列化一个对象。该方式适用于所有实现了java.io.Serializable的类。通过继承java.io.Externalizable,你能进一步控制序列化的性能。Java序列化非常灵活,但是速度较慢,在某些情况下序列化的结果也比较大。
Kryo序列化
Spark也能使用Kryo(版本2)序列化对象。Kryo不但速度极快,而且产生的结果更为紧凑(通常能提高10倍)。Kryo的缺点是不支持所有类型,为了更好的性能,你需要提前注册程序中所使用的类(class)。
可以在创建SparkContext之前,通过调用System.setProperty("spark.serializer", "spark.KryoSerializer"),将序列化方式切换成Kryo。
但是Kryo需要用户进行注册,这也是为什么Kryo不能成为Spark序列化默认方式的唯一原因,但是建议对于任何“网络密集型”(network-intensive)的应用,都采用这种方式进行序列化方式。
#####44. 什么是累加器?
> 累加器用来把Executor端变量信息聚合到Driver端。在Driver程序中定义的变量,在Executor端的每个Task都会得到这个变量的一份新的副本,每个task更新这些副本的值后,传回Driver端进行merge(合并)。
> 当内置的Accumulator无法满足要求时,可以继承AccumulatorV2实现自定义的累加器。
> 实现自定义累加器的步骤:
> 1. 继承AccumulatorV2,实现相关方法
> 2. 创建自定义Accumulator的实例,然后在SparkContext上注册它
#####45. 什么是广播变量?
广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个 Spark 操作使用
使用广播变量的过程如下:
(1)通过对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T]
对象。 任何可序列化的类型都可以这么实现。
(2)通过 value 属性访问该对象的值(在 Java 中为 value() 方法)。
(3)变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节 点)。
#####46. 节点和task 执行的关系?
1) 每个节点可以起一个或多个Executor。
2) 每个Executor由若干core组成,每个Executor的每个core一次只能执行一个Task。
3) 每个Task执行的结果就是生成了目标RDD的一个partiton。
注意: 这里的core是虚拟的core而不是机器的物理CPU核,可以理解为就是Executor的一个工作线程。而 Task被执行的并发度 = Executor数目 * 每个Executor核数。
#####47. diver 失败了如何恢复?
1. --supervise 在集群模式下,driver 失败了自动重启
2. 对driver 的元数据做ck
#####48. cluster 模式如何查看日志?
第一种 是聚合日志方式(推荐,比较常用)
这种方式的话,顾名思义,就是说,将散落在集群中各个机器上的日志,最后都给聚合起来,让我们可以统一查看
如果打开了日志聚合的选项,即yarn.log-aggregation-enable,container的日志会拷贝到hdfs上去,并从机器中删除
对于这种情况,可以使用yarn logs -applicationId <app ID>命令,来查看日志
yarn logs命令,会打印出application对应的所有container的日志出来,当然,因为日志是在hdfs上的,我们自然也可以通过hdfs的命令行来直接从hdfs中查看日志
日志在hdfs中的目录,可以通过查看yarn.nodemanager.remote-app-log-dir和yarn.nodemanager.remote-app-log-dir-suffix属性来获知
第二种 web ui(如果你有精力的话,可以去配一下)
日志也可以通过spark web ui来查看executor的输出日志
但是此时需要启动History Server,需要让spark history server和mapreduce history server运行着
并且在yarn-site.xml文件中,配置yarn.log.server.url属性
spark history server web ui中的log url,会将你重定向到mapreduce history server上,去查看日志
第三种 分散查看(通常不推荐)
如果没有打开聚合日志选项,那么日志默认就是散落在各个机器上的本次磁盘目录中的,在YARN_APP_LOGS_DIR目录下
根据hadoop版本的不同,通常在/tmp/logs目录下,或者$HADOOP_HOME/logs/userlogs目录下
如果你要查看某个container的日志,那么就得登录到那台机器上去,然后到指定的目录下去,找到那个日志文件,然后才能查看
#####49. spark的优化? 10条以上
为什么需要调优??
程序都是能跑的,集群还是那个集群,但是有可能另外一个会调优的人和你写的代码的运行的速度要几倍甚至几十倍
1.开发调优
1.1 原则一:避免创建重复的RDD
我们有一份数据 ,student.txt
第一个需求 :wordCount val stuRDD = sc.textFile("e://sparkData//student.txt")
第二个需求:算有多少个学生 val stuRDD01 = sc.textFile("e://sparkData//student.txt")
如果创建两份就会加载两次,浪费性能。但是根据我们的需要来说,同样的算子,需要使用两次,那怎么办??
进行持久化即可:
sc.textFile("e://sparkData//student.txt").cache()
1.2 原则二:尽可能使用同一个RDD
这种是大家在开发中,经常写着写着就忘记了
举个例子:
val namesRDD = starsRDD.map(_._1)
val name2LengthRDD = namesRDD.map(name => (name,name.length))
// 这两个map是可以合并的
val name2LengthRDD01 = starsRDD.map(tuple => (tuple._1,tuple._1.length))
下面的这种方式写RDD性能更优,因为减少了一次RDD的计算
1.3 原则三:对多次使用的RDD进行持久化
要注意的持久化级别的选择:
1.优先采用MEMORY_ONLY,d但是前提是你的内存足够大,否则可能导致OOM(out of memory 异常)
2.如果MEMORY_ONLY内存不足,就采用MEMORY_ONLY_SER持久化级别,序列化之后,把数据占用的内存变少了, 但是序列化和之后使用的反序列化得消耗cpu
3.以上是纯内存持久化,速度很快,但是如果MEMORY_ONLY_SER还是内存不够,那么就采用 MEMORY_AND_DISK_SER,采用这种策略,会优先的把数据放在内存中,内存不足放入磁盘
4.不建议使用纯的DISK方案,这样很慢,_2在一些特殊场景(Spark Streaming 容错要求更高)使用以外,一般 不建议
1.4 原则四:尽量避免使用shuffle类算子
减少分区
Broadcast + map + filter 代替 join
对于join,大表join小表,可以考虑把小表的数据广播到executor中,通过map + filter的操作完成join的功 能
1.5 原则五:使用map-side预聚合的shuffle操作
定要使用shuffle操作,无法用map类的算子来替代,那么尽量使用可以map-side预聚合的算子。
就是用reduceByKey 代替groupByKey
如果同样的一个需求,用reduceByKey的性能比groupByKey好很多,可以大大减少数据的网络传输
1.6 原则六:使用高性能的算子
有些需求,很多算子都能使用,但是性能不一样,用性能更高算子解决
例如:
使用reduceByKey/aggregateByKey替代groupByKey
使用mapPartitions替代普通map,使用mapPartitions会出现OOM(内存溢出)的问题
使用foreachPartitions替代foreach,类似mapPartitions替代普通map,相比于上面的来说 ,这是一个 action算子 ,读写数据库例子
使用filter之后进行coalesce操作
补充:repartition 和 coalesce 的使用场景
repatriation有shuffle,一般是把分区数变多,目的提高并行度
val rdd02 = rdd01.filter(xxx) --> 有的分区会过滤很多,有的可能过滤的很少
coalesce 一般来说是把分区数变少,就是把分区数合并,
rdd02.coalesce() ,并行度虽然降低了,但是资源利用率更高,反而可能提高性能
如果需要减少的分区特别少,
rdd01 是20个分区--》rdd02: 5个分区, val rdd02 = rdd01.coalesce(5,true)/rdd01.repartition(5),这样比较好
1.7 原则七:广播大变量
好处是:
如果使用的外部变量比较大,建议使用Spark的广播功能,对该变量进行广播。广播后的变量,会保证每个 Executor的内存中,
只驻留一份变量副本,Executor有多个,而Executor中的task执行时共享该Executor中的那份变量副本。这样 的话,
可以大大减少变量副本的数量,从而减少网络传输的性能开销,并减少对Executor内存的占用开销,降低GC的频 率。
1.8 原则八:使用Kryo优化序列化性能
spark的序列化? java Kryo
做如下配置:
//创建SparkConf对象。
val conf =new SparkConf().setMaster(...).setAppName(...)
//设置序列化器为KryoSerializer。
conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer")
//注册要序列化的自定义类型。
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
1.9 原则九:优化数据结构
对象,字符串,集合都比较占用内存
字符串代替对象
数组代替集合
使用原始类型(比如Int、Long)替代字符串
使用起来太难,不实用
2.0 资源调优
在executor里面,内存会被分为几个部分:
第一块是让task执行我们自己编写的代码时使用,默认是占Executor总内存的20%;
第二块是让task通过shuffle过程拉取了上一个stage的task的输出后,进行聚合等操作时使用,默认也是占 Executor总内存的20%;
spark.shuffle.memoryFraction
用来调节executor中,进行数据shuffle所占用的内存大小默认是0.2
第三块是让RDD持久化时使用,默认占Executor总内存的60%。
spark.storage.memoryFraction
用来调节executor中,进行数据持久化所占用的内存大小,默认是0.6
2.1
理解
补充:spark如何配置参数
1.在代码中如何配置参数:
conf.set(key,value)
conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer")
对于资源调优,有下面几个参数:
num-executors
作业中,一共给多少个executor
参数调优建议:每个Spark作业的运行一般设置50~100个左右的Executor进程比较合适,设置太少或太多的Executor进程都不好。
设置的太少,无法充分利用集群资源;设置的太多的话,大部分队列可能无法给予充分的资源。
executor-memory
参数说明:该参数用于设置每个Executor进程的内存。Executor内存的大小,很多时候直接决定了Spark作业的性能,
而且跟常见的JVM OOM异常,也有直接的关联。
640G内存 32g * 20 = 640G
20个executor
可以看看自己团队的资源队列的最大内存限制是多少,num-executors乘以executor-memory,就代表了你的Spark作业申请到的总内存量(也就是所有Executor进程的内存总和),
这个量是不能超过队列的最大内存量的。此外,如果你是跟团队里其他人共享这个资源队列,那么申请的总内存量最好不要超过资源队列最大总内存的1/3~1/2,避免你自己的Spark作业
占用了队列所有的资源,导致别的同学的作业无法运行。
executor-cores
每个executor有多少个cpu 核心
这个核心指的并不是物理核心,指的是逻辑核心
i7 4核8线程 16/32
参数调优建议:Executor的CPU core数量设置为2~4个较为合适。同样得根据不同部门的资源队列来定,可以看看自己的资源队列的最大CPU core限制是多少,再依据设置的Executor数量,
来决定每个Executor进程可以分配到几个CPU core。同样建议,如果是跟他人共享这个队列,那么num-executors * executor-cores不要超过队列总CPU core的1/3~1/2左右比较合适,
也是避免影响其他同学的作业运行。
driver-memory
给dirver程序分配的内存,当有collect操作的时候,需要把dirver的内存给大一点
spark.default.parallelism
参数说明:该参数用于设置每个stage的默认task数量。这个参数极为重要,如果不设置可能会直接影响你的Spark作业性能。
spark.default.parallelism = num-executors * executor-cores (2--3倍)
这样设置之后,那么每个cpu 都是有2-3个task
10个executor 每个executor里面有4个core ,设置spark.default.parallelism = 120
运行executor的core有多少个 40个 ,120/40 = 每个core的task
task的总数,肯定得比分配到cpu core的数量多, 反之浪费资源,一般就是2-3倍比较合适
如何设置并行度:
如何设置一个Spark Application的并行度?
1. spark.defalut.parallelism 默认是没有值的,如果设置了值比如说10,是在shuffle的过程才会起作用(val rdd2 = rdd1.reduceByKey(_+_) //rdd2的分区数就是10,rdd1的分区数不受这个参数的影响)
new SparkConf().set(“spark.defalut.parallelism”,”500“)
2、如果读取的数据在HDFS上,增加block数,默认情况下split与block是一对一的,而split又与RDD中的partition对应,所以增加了block数,也就提高了并行度。
3、RDD.repartition,给RDD重新设置partition的数量
4、reduceByKey的算子指定partition的数量
val rdd2 = rdd1.reduceByKey(_+_,10) val rdd3 = rdd2.map.filter.reduceByKey(_+_)
5、val rdd3 = rdd1.join(rdd2) rdd3里面partiiton的数量是由父RDD中最多的partition数量来决定,因此使用join算子的时候,增加父RDD中partition的数量。
6、spark.sql.shuffle.partitions //spark sql中shuffle过程中partitions的数量
3.spark调优数据倾斜调优
map filter 这种会发生数据倾斜吗??
问题:数据倾斜肯定是某些key发生了数据倾斜,那么如何知道是哪些key倾斜了??
1000万条取2万条出来
val sampledPairs = pairs.sample(false, 0.1)
val sampledWordCounts = sampledPairs.countByKey()
sampledWordCounts.foreach(println(_))
解决:
1.打散
2.过滤
2.脚本里面可以配置参数
使用格式:
./bin/spark-submit \
--class <main-class> \
--master <master-url> \
--deploy-mode <deploy-mode> \
--conf <key>=<value> \
... # other options
<application-jar> \
举例:./bin/spark-submit \
--master yarn-cluster \
--num-executors 100 \
--executor-memory 6G \
--executor-cores 4 \
--driver-memory 1G \
--conf spark.default.parallelism=1000 \
--conf spark.storage.memoryFraction=0.5 \
--conf spark.shuffle.memoryFraction=0.3 \
3.在配置文件中调节参数
conf/spark-defaults.conf配置文件中读取配置选项。 在conf/spark-defaults.conf配置文件中,
每行是key-value对,中间可以是用空格进行分割,也可以直接用等号进行分割
问题 :这三个地方都可以配置参数 ,对于同样的一个参数在三个地方都配置了,而且参数的value不一样,那么
到底是那个生效??
优先级 :
在代码中的优先级最高 --》 意味一旦写入,在其他地方都不能修改了,除非出现修改代码,打包运行,这样不可取,除非有些参数写入就不修改了,在这里面配置比较合适
在脚本中优先级其次 --》 非常灵活,一般来说比较适合在这里面写入参数
在配置文件中最低 --》 以上两种的配置参数,都是针对于该应用的参数,配置文件是全局的参数,优先级最低,更加适合写有些全部都需要用到的参数
2.资源调优
3.数据倾斜调优
4.shuffle调优几个部分
//////////////////////////////////
spark 优化原则
1.尽量让计算操作在一个rdd里面进行
// 错误的做法。
// 有一个<Long, String>格式的RDD,即rdd1。
// 接着由于业务需要,对rdd1执行了一个map操作,创建了一个rdd2,而rdd2中的数据仅仅是rdd1中
的value值而已,也就是说,rdd2是rdd1的子集。
JavaPairRDD<Long, String> rdd1 = ...
JavaRDD<String> rdd2 = rdd1.map(...)
// 分别对rdd1和rdd2执行了不同的算子操作。
rdd1.reduceByKey(...)
rdd2.map(...)
说明: rdd2 是 k-v类型的rdd1 的v 经过某个操作转变过来的
正确做法:
JavaPairRDD<Long, String> rdd1 = ... .Cache()
rdd1.reduceByKey(...)
rdd1.map(tuple._2...)
减少了RDD的生成
2. 尽量减少shuffle
// 传统的join操作会导致shuffle操作。
// 因为两个RDD中,相同的key都需要通过网络拉取到一个节点上,由一个task进行join操作。
val rdd3 = rdd1.join(rdd2)
// Broadcast+map的join操作,不会导致shuffle操作。
// 使用Broadcast将一个数据量较小的RDD作为广播变量。
val rdd2Data = rdd2.collect()
val rdd2DataBroadcast = sc.broadcast(rdd2Data)
// 在rdd1.map算子中,可以从rdd2DataBroadcast中,获取rdd2的所有数据。
// 然后进行遍历,如果发现rdd2中某条数据的key与rdd1的当前数据的key是相同的,那么就判定可以
进行join。
// 此时就可以根据自己需要的方式,将rdd1当前数据与rdd2中可以连接的数据,拼接在一起(String
或Tuple)。
val rdd3 = rdd1.map(rdd2DataBroadcast...)
// 注意,以上操作,建议仅仅在rdd2的数据量比较少(比如几百M,或者一两G)的情况下使用。
// 因为每个Executor的内存中,都会驻留一份rdd2的全量数据。
#####50. excutor内存的分配?
> 在executor里面,内存会被分为几个部分:
> 第一块是让task执行我们自己编写的代码时使用,默认是占Executor总内存的20%;
> 第二块是让task通过shuffle过程拉取了上一个stage的task的输出后,进行聚合等操作时使用,默认也是占Executor总内存的20%;
> spark.shuffle.memoryFraction
> 用来调节executor中,进行数据shuffle所占用的内存大小默认是0.2
> 第三块是让RDD持久化时使用,默认占Executor总内存的60%。
> spark.storage.memoryFraction
> 用来调节executor中,进行数据持久化所占用的内存大小,默认是0.6
#####51. Rdd partition的个数有什么来决定的?
1. 默认的
2. 指定的
3. 从hdfs 读取数据,由块的个数
4. 从kafka读取数据。由topic 的partition个数决定
#####52. 总结 spark Shuffle?
https://blog.csdn.net/young_0609/article/details/89643087
一、未经优化的HashShuffleManager
shuffle write:
stage结束之后,每个task处理的数据按key进行“分类”
数据先写入内存缓冲区
缓冲区满,溢出到磁盘文件
最终,相同key被写入同一个磁盘文件
创建的磁盘文件数量 = 当前stagetask数量 * 下一个stage的task数量
shuffle read:
从上游stage的所有task节点上拉取属于自己的磁盘文件
每个read task会有自己的buffer缓冲,每次只能拉取与buffer缓冲相同大小的数据,然后聚合,聚合完一批后拉取下一批
该拉取过程,边拉取边聚合
二、Sort shuffle
shuffle过程容易出现的主要问题就是内存溢出和频繁的IO操作,导致程序异常和特别慢
1、写入内存数据结构
注意:
shuffle中的定时器:定时器会检查内存数据结构的大小,如果内存数据结构空间不够,那么会申请额外的内存。申请到了,内存数据结构的大小变大,内存不够,申请不到,则发生溢写
2、排序
在溢写到磁盘文件之前,会先根据key对内存数据结构中已有的数据进行排序。
3、溢写
排序过后,会分批将数据写入磁盘文件。默认的batch数量是10000条,也就是说,排序好的数据,会以每批1万条数据的形式分批写入磁盘文件。
4、merge
一个task将所有数据写入内存数据结构的过程中,会发生多次磁盘溢写操作,也就会产生多个临时文件。
最后会将之前所有的临时磁盘文件都进行合并,这就是merge过程,
此时会将之前所有临时磁盘文件中的数据读取出来,然后依次写入最终的磁盘文件之中。
此外,由于一个task就只对应一个磁盘文件,也就意味着该task为Reduce端的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 sort shuffle
1、
bypass运行机制的触发条件如下:不需要排序和聚合的shuffle操作。
1)shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值。
2)不是聚合类的shuffle算子(比如reduceByKey)。
此时task会为每个reduce端的task都创建一个临时磁盘文件,并将数据按key进行hash然后根据key的hash值,
将key写入对应的磁盘文件之中。当然,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后再溢写到磁盘文件的。
最后,同样会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。
该过程的磁盘写机制其实跟未经优化的HashShuffleManager是一模一样的,因为都要创建数量惊人的磁盘文件,
只是在最后会做一个磁盘文件的合并而已。因此少量的最终磁盘文件,也让该机制相对未经优化的HashShuffleManager来说,
shuffle read的性能会更好。
53. sparkStreaming 的核心抽象?
> - 一旦启动上下文,就无法设置新的流计算或将其添加到该流计算中
> - 上下文停止后,将无法重新启动
> - JVM 中只能同时激活一个 StreamingContext
> - StreamingContext 上的 stop() 也会停止 SparkContext,如要仅停止 StreamingContext,请将名为 stopSparkContext 的 stop() 的可选参数设置为 false
> - 只要在创建下一个 StreamingContext 之前停止了上一个 StreamingContext(不停止 SparkContext),就可以重复使用 SparkContext 创建多个 StreamingContext
54. 什么是DStream?
一连串不间断的batch,一个batch就是一个时间段的Rdd
DStream 由一系列连续的 RDD 表示,这是 Spark 对不可变的分布式数据集的抽象
##### 55.sparkStreaming和Storm对比?
> Storm
>
> 1. 实时计算模型 纯实时,来一条数据,处理一条数据
> 2. 实时计算延迟度 毫秒级
> 3. 吞吐量 低
> 4. 事务机制 支持完善
> 5. 健壮性 / 容错性 ZooKeeper,Acker,非常强
> 6. 动态调整并行度 支持
>
> Spark Streaming
>
> 1. 实时计算模型 准实时,对一个时间段内的数据收集起来,作为一个RDD,再处理
> 2. 实时计算延迟度 秒级
> 3. 吞吐量 高
> 4. 事务机制 支持,但不够完善
> 5. 健壮性 / 容错性 Checkpoint,WAL,一般
> 6. 动态调整并行度 不支持
##### 56.sparkStreaming的算子类型?
> Transformations 和 Output
##### 57.sparkStreaming 的receiver ?
> 会启动一个线程单独拉取数据
> 给定的cpu核数大于需要计算的流数
##### 58.sparkStreaming从kafka 获取数据的两种方式?
> - [Receiver](https://www.cnblogs.com/heml/p/6796414.html#_label0)
> - [Direct](https://www.cnblogs.com/heml/p/6796414.html#_label1)
> - [Direct代码](https://www.cnblogs.com/heml/p/6796414.html#_label2)
>
> 简单理解为:Receiver方式是通过zookeeper来连接kafka队列,Direct方式是直接连接到kafka的节点上获取数据
>
> 1、拉取数据的方式
>
> Receiver采用kafka高级api,一次性拉取固定时间的数据后再进行处理,这可能造成一个问题:拉取的数据过多,放不下怎么办?
>
> Direct采用kafka低级api,直接连接到kafka的分区,rdd中的分区与kafka中的分区是一一对应的,他是一边拉取数据,一边处理数据,到达设置的时间间隔后,就作为一个批次进行计算结果。
>
> 2、可靠性保证
>
> Receiver要保证数据不丢失,需要WAL,提供至少一次的语义。
>
> Direct可以提供一次且紧一次语义。
>
> 3、 高峰数据量过大的处理
>
> Receiver方式只能手动设置最大接收速率,不能自动调节数据接收速率。
>
> Direct可以使用反压机制自动调节数据接收速率。
>
> 4、直连方式的区别
>
> Receiver接收固定时间间隔的数据(放在内存中),使用Kafka高级的API,自动维护偏移量,达到固定的时间才处理,效率低且容易丢失数据;
>
> Direct直连方式,相当于直接连接到Kafka分区上,使用Kafka底层的API,需要自己维护偏移量,效率高。
##### 59.sparkStreaming从kafka 获取的 message 包含哪些?
record.checksum() // 记录的校验和
record.offset() // 偏移量
record.partition() // 分区
record.timestamp() // 时间戳
record.timestampType() // 时间戳类型
record.topic() // 主题
record.key() // 键(如果未指定键,则为null)
record.hashCode() // 字符串的哈希码
record.value().toString //值