1. 简述Spark中7种存储级别 ?
在Spark中,存储级别(Storage Level)定义了RDD或DataFrame的数据在不同计算阶段是存储在内存中、磁盘上,或者两者的组合。以下是Spark中7种内置的存储级别:
-
StorageLevel.OFF_HEAP:数据既不存储在JVM堆内存中,也不存储在堆外内存中。这通常用于存储二进制数据,如图片或PDF文件。
-
StorageLevel.NONE:数据不存储在内存或磁盘上。一旦数据被使用,它就会消失。这通常用于不需要持久化或可以重新计算的数据。
-
StorageLevel.DISK_ONLY:数据仅存储在磁盘上,不存储在内存中。这适用于大量数据,需要节省内存但可以牺牲一些I/O性能。
-
StorageLevel.MEMORY_ONLY:数据仅存储在JVM堆内存中。如果内存不足,数据将不会被存储,而是在下次使用时重新计算。
-
StorageLevel.MEMORY_ONLY_SER:数据序列化后仅存储在JVM堆内存中。序列化可以减少内存占用,但会增加CPU使用率,因为需要序列化和反序列化数据。
-
StorageLevel.MEMORY_AND_DISK:数据首先尝试存储在JVM堆内存中,如果内存不足,则溢出到磁盘。这适用于需要持久化但内存有限的情况。
-
StorageLevel.OFF_HEAP:数据存储在JVM堆外内存中。这允许应用程序使用超出JVM堆大小的内存,适用于需要大量内存但希望避免JVM垃圾收集影响的场景。
除了这些内置存储级别,Spark还允许用户自定义存储级别,以满足特定的存储需求。存储级别的选择会影响应用程序的性能和资源使用,因此在设计Spark应用程序时,合理选择存储级别是非常重要的。
2. 简述Spark分哪几个部分(模块)?分别有什么作用(做什么,自己用过哪些,做过什么) ?
Apache Spark是一个庞大的生态系统,由多个模块组成,每个模块都承担着特定的功能。以下是Spark的主要模块及其作用:
-
Spark Core:
- 核心模块,提供了基本的分布式数据操作功能,如RDD(弹性分布式数据集)的创建、转换和行动操作。它是其他所有Spark模块的基础。
-
Spark SQL:
- 提供了对结构化数据的处理能力,支持SQL查询和DataFrame/Dataset API。它允许用户以声明式方式进行数据查询和转换。
-
Spark Streaming:
- 流处理模块,允许用户处理实时数据流。它支持各种数据源,如Kafka、Flume等,并提供了DStream(离散数据流)的概念。
-
MLlib:
- 是Spark的机器学习库,提供了一系列的机器学习算法和工具,包括分类、回归、聚类等,以及模型评估和数据预处理功能。
-
GraphX:
- 图处理模块,提供了对图的创建、查询和处理的功能。它支持Pregel API,允许用户实现自定义的图算法。
-
SparkR:
- 提供了对R语言的接口,允许R语言用户使用Spark进行分布式计算。
-
PySpark:
- 提供了对Python语言的接口,允许Python用户使用Spark进行数据处理和分析。
-
Spark Submit:
- 是一个命令行工具,用于提交Spark应用程序到集群运行。
-
Spark Shell:
- 类似于一个交互式命令行界面,允许用户以交互方式执行Spark命令和操作。
-
Thrift JDBC/ODBC Server:
- 提供了JDBC和ODBC接口,允许用户通过标准的数据库连接方式访问Spark集群。
-
Mesos Fine-Grained Scheduling Mode:
- 允许Spark与Mesos集群的细粒度资源调度模式集成,以更有效地利用集群资源。
-
Kubernetes Integration:
- 提供了与Kubernetes集群的集成,允许Spark应用程序在Kubernetes上运行。
使用场景:
- 使用Spark Core进行基础的分布式数据处理任务,如数据过滤、映射和聚合操作。
- 使用Spark SQL进行结构化数据查询和转换,以及通过DataFrame API进行高效数据处理。
- 使用Spark Streaming处理实时数据流,如从Kafka接收数据流并进行实时分析。
- 使用MLlib进行机器学习任务,如模型训练、预测和评估。
- 使用GraphX处理图数据,执行图算法,如PageRank或社区检测。
- 使用PySpark 或SparkR进行特定语言的数据处理和分析,根据用户熟悉的编程语言选择。
每个模块都针对特定的数据处理和分析任务进行了优化,使得Spark成为一个多功能的大数据处理平台。
3. RDD的宽依赖和窄依赖,举例一些算子 ?
在Spark中,RDD(弹性分布式数据集)的转换操作可以根据它们是否需要跨分区的数据交换被分为窄依赖(Narrow Dependency)和宽依赖(Wide Dependency)。
窄依赖(Narrow Dependency)
窄依赖意味着子RDD的每个分区只依赖于父RDD的一个或少数几个分区的数据。这种依赖关系不需要跨节点的数据交换(Shuffle),因此通常更高效。以下是一些创建窄依赖的算子:
- map:对RDD的每个元素应用一个函数,返回一个新的RDD。
- filter:根据给定的条件过滤RDD中的元素。
- union:将两个RDD合并为一个,不改变RDD的分区。
- sample:随机采样RDD中的元素,可以是有偏或无偏采样。
- mapPartitions:对RDD的每个分区应用一个函数,返回一个新的RDD,该函数接收一个迭代器并返回一个迭代器。
- mapPartitionsWithIndex :类似于
mapPartitions
,但函数还接收分区索引作为参数。 - mapValues:在(K,V)类型的RDD中,只对值应用一个函数。
宽依赖(Wide Dependency)
宽依赖意味着子RDD的每个分区可能依赖于父RDD的所有分区的数据。这种依赖关系通常需要跨节点的数据交换(Shuffle),可能会增加作业的执行时间。以下是一些创建宽依赖的算子:
- groupByKey:按照键对RDD中的元素进行分组,生成一个(K, [V])类型的RDD。
- reduceByKey :类似于
groupByKey
,但会合并具有相同键的所有值。 - aggregateByKey:对每个键的值应用一个自定义的聚合函数。
- join:根据两个RDD中的键进行内连接,生成一个(K, (V1, V2))类型的RDD。
- leftOuterJoin:左外连接,返回左侧RDD的所有元素,如果右侧没有匹配的键,则对应值为空。
- rightOuterJoin:右外连接,返回右侧RDD的所有元素,如果左侧没有匹配的键,则对应值为空。
- fullOuterJoin:全外连接,返回两个RDD中所有的元素,没有匹配的键对应的值为空。
- cogroup:对每个键的值列表进行分组,返回一个(K, ([V1], [V2]))类型的RDD。
- repartition:重新分区RDD,可能会改变数据在集群中的分布。
- coalesce:减少RDD的分区数,但不保证数据的分布方式。
窄依赖和宽依赖的选择对Spark作业的性能有重要影响。窄依赖操作通常更高效,因为它们不需要数据Shuffle,而宽依赖操作可能会因为数据Shuffle而导致性能下降。在设计Spark应用程序时,合理选择和使用这些算子是非常重要的。
4. Spark SQL的GroupBy会造成窄依赖吗 ?
在Spark SQL中,GroupBy
操作本身通常会产生宽依赖,因为它涉及到数据的Shuffle操作。宽依赖意味着父RDD的每个分区的数据可能会被多个子RDD的分区所使用,这就需要跨节点进行数据传输。
然而,有一种特殊情况,当使用map-side combine
优化时,可以减少数据的Shuffle量。map-side combine
是一种优化技术,它在进行GroupBy
操作之前,首先在每个Mapper节点上对数据进行局部聚合,然后再进行全局聚合。这样可以减少需要通过网络传输的数据量,从而减少Shuffle的开销。
具体来说:
-
没有map-side combine :普通的
GroupBy
操作会产生宽依赖,因为它需要将所有具有相同键的数据聚集到一起,这通常涉及到大量的数据在不同节点之间的传输。 -
使用map-side combine:如果启用了map-side combine,Spark会首先在每个节点上对数据进行局部聚合,然后再将局部聚合的结果传输到其他节点上进行最终的聚合。这样,尽管最终聚合阶段仍然是宽依赖,但数据的Shuffle量会大大减少。
在Spark SQL中,可以通过设置配置参数spark.sql.autoBroadcastJoinThreshold
来控制是否使用map-side combine。如果被聚合的表的大小小于这个阈值,Spark SQL会自动应用map-side combine优化。
总结来说,标准的GroupBy
操作在Spark SQL中会造成宽依赖,但如果使用map-side combine优化,可以减少数据的Shuffle,从而降低宽依赖的影响。
5. 简述GroupBy是行动算子吗 ?
在Spark中,groupBy
操作通常指的是groupByKey
或groupBy
(在DataFrame API中)。这些操作本身是转换(Transformation)操作,而不是行动(Action)操作。
转换操作(Transformation)
转换操作是对RDD或DataFrame进行的,它们定义了一个新的分布式数据集,但不立即触发计算。Spark会将这些转换操作链接起来,形成一个依赖链,直到遇到一个行动操作。以下是转换操作的一些特点:
- 惰性执行:转换操作不会立即执行,而是在遇到行动操作时才触发计算。
- 构建DAG:转换操作构建了一个有向无环图(DAG),描述了数据转换的逻辑。
- 优化机会:Spark的优化器可以在行动操作之前对整个转换操作链进行优化。
行动操作(Action)
行动操作是触发实际计算的指令,它们告诉Spark需要执行计算并返回结果。以下是行动操作的一些特点:
- 触发执行:行动操作会导致Spark执行从根RDD开始的所有转换操作。
- 返回结果:行动操作通常返回一个结果给驱动程序(Driver)。
- 阻塞性:行动操作是阻塞性的,它们会等待计算完成。
groupByKey
和 groupBy
groupByKey
:是一个转换操作,它按照键对RDD中的元素进行分组,生成一个(K, V)类型的RDD,其中每个键对应的值是一个包含该键所有值的迭代器。groupBy
:在DataFrame API中,groupBy
是一个转换操作,它允许按照一个或多个列对数据进行分组,并返回一个GroupedData
对象,可以进一步使用聚合函数。
在实际使用中,groupByKey
或groupBy
后面通常会跟随一个行动操作,如count()
、collect()
、take()
等,这些行动操作会触发整个转换链的执行,并返回最终结果。
6. 简述Spark的宽依赖和窄依赖,为什么要这么划分 ?
在Apache Spark中,依赖关系定义了不同RDD(弹性分布式数据集)之间的数据传递和转换方式。依赖关系分为两种类型:窄依赖(Narrow Dependency)和宽依赖(Wide Dependency)。以下是这两种依赖关系的简述以及为什么要进行这样的划分:
窄依赖(Narrow Dependency):
- 定义 :窄依赖指的是一个父RDD的分区只被子RDD的一个分区使用。例如,
map
、filter
和union
操作都是窄依赖,因为每个输出分区只依赖于一个输入分区。 - 特点:窄依赖不需要数据在不同分区或节点之间进行Shuffle(重新分配),因此可以减少网络传输的开销,提高计算效率。
宽依赖(Wide Dependency):
- 定义 :宽依赖是指一个父RDD的分区被多个子RDD的分区使用。例如,
groupByKey
、reduceByKey
和join
操作都是宽依赖,因为一个键可能在多个分区中出现,需要将所有相同键的数据聚集到一起。 - 特点:宽依赖需要进行数据Shuffle,因为数据需要根据某种键进行重新分配。这通常涉及到网络传输和可能的磁盘I/O,因此在性能上可能比窄依赖操作更昂贵。
为什么要进行这样的划分?
- 性能优化:窄依赖和宽依赖的划分有助于Spark调度器优化作业的执行。通过识别窄依赖,Spark可以设计更高效的执行计划,减少数据的移动和Shuffle操作。
- 资源分配:窄依赖可以更好地利用数据本地性,因为它不需要跨节点的数据传输。这有助于减少网络负载和提高计算资源的利用率。
- 容错能力:窄依赖的容错处理通常更简单,因为每个子分区只依赖于一个父分区,即使父分区丢失,也只需要重新计算该分区即可。
- 执行计划:依赖关系的划分使得Spark可以更智能地规划数据的存储和处理,例如,通过持久化(缓存)那些在多个阶段中被重复使用的RDD来避免重复计算。
总的来说,窄依赖和宽依赖的划分是Spark优化作业执行、提高性能和资源利用率的重要机制。通过理解这两种依赖关系,开发者可以更好地设计和调整他们的Spark应用程序,以实现更高的计算效率。