Spark是一个快速而通用的大数据处理框架,它提供了高效的分布式数据处理和分析能力。
Spark虽然不是Hadoop的一部分,但与Hadoop生态系统紧密集成。Spark提供了更快的数据处理和分析能力,具备批处理、流处理、机器学习和图计算等功能,spark可以理解为Hadoop中MapReduce的升级后的计算模型。
Hadoop | Spark | |
---|---|---|
类型 | 完整的分布式基础平台,支持计算、存储、调度等 | 分布式计算工具,可用于迭代计算, 交互式计算, 流计算 |
生态系统 | 拥有Hive、Pig、HBase、Sqoop等完整的大数据处理和分析生态 | 支持与Hive、Pig、HBase、kafka等的集成;支持跨平台,可以与各种数据存储和处理系统集成 |
对机器要求 | 低 | 对内存要求很高 |
数据处理模型 | 使用MapReduce模型进行数据处理,数据通过HDFS分布在多个节点上,并在每个节点上进行Map和Reduce操作 | 使用 RDD 抽象来表示和处理分布式数据集,采用基于内存的计算模式,通过内存计算提高了数据分析和处理的速度。 |
计算速度 | MapReduce模型适合处理批处理任务,但是在迭代计算和实时数据处理上因为磁盘IO比较慢,速度相对较慢 | Spark通过内存计算和更高级别的数据抽象(如RDD、DataFrames和Datasets)提供了更快的计算速度,在迭代计算、流处理和交互式查询上具有优势 |
数据处理灵活性 | Hadoop的HDFS,适用于长期存储和批量处理,但对于复杂的数据处理需求,需要借助其他工具和技术,如Hive和Pig | Spark支持批处理、流处理、机器学习和图计算等,且提供了高级API(如Spark SQL)和专门的库(如MLlib和GraphX)进行更复杂的数据分析和处理 |
数据存储结构 | MapReduce 中间计算结果存在 HDFS 磁盘上, 延迟大 | RDD 中间运算结果存在内存中 , 延迟小 |
编程范式 | Map+Reduce, API 较为底层, 算法适应性差 | RDD 组成 DAG 有向无环图, API 较为顶层, 方便使用 |
运行方式 | Task 以进程方式维护, 任务启动慢 | Task 以线程方式维护, 任务启动快 |
RDD(Resilient Distributed Datasets):弹性分布式数据集, 是Spark的核心数据抽象, 代表一个可分区、可并行计算的不可变数据集。RDD提供了弹性的容错性、数据分区和数据转换操作,是实现分布式计算的基本单元。
RDD源码中有这么一段描述:
A list of partitions
A function for computing each split
A list of dependencies on other RDDs
Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
Optionally, a list of preferred locations to compute each split on(e.g. block locations for an HDFS file)
上面的描述一次对应Spark的分区列表、计算函数、依赖关系、分区函数、最佳位置。
RDD的一些关键特性和使用方式:
分区(Partitioning):RDD将数据集划分为多个逻辑分区(logical partitions),并将这些分区分布在集群中的多个节点上。分区的数量决定了并行计算的程度。
弹性(Resilience):RDD具备弹性特性,即当某个分区的数据或节点发生故障时,Spark可以通过血缘关系(lineage)重新计算丢失的分区,从而实现了容错性。
不可变性(Immutability):RDD是不可变的,一旦创建就不能被修改。若要对RDD进行转换或操作,需要生成一个新的RDD。
转换操作(Transformations):RDD提供了一系列的转换操作,例如map、filter、reduce、join等。通过这些转换操作,可以从一个RDD中创建另一个具有不同数据结构或内容的RDD,实现数据转换和处理。
动作操作(Actions):RDD提供了一些动作操作,如count、collect、reduce等。动作操作会触发实际的计算,并返回结果给驱动程序或将结果保存到外部存储系统。
数据持久化(Data Persistence):RDD支持通过将数据缓存在内存中来加速数据访问。可以选择将RDD持久化到内存、磁盘或者以序列化的方式存储。
RDD创建有三种方法:
1.1 从集合创建RDD, 使用SparkContext的parallelize方法:
val sparkConf = new SparkConf().setAppName("RDD creation")
val sc = new SparkContext(sparkConf)
val data = List(1, 2, 3, 4, 5)
val rdd = sc.parallelize(data)
1.2 从外部存储创建RDD, 适用SparkContext的textFile方法
val sparkConf = new SparkConf().setAppName("RDD creation")
val sc = new SparkContext(sparkConf)
// 指定读取文件路径或包含文件的目录路径
val rdd = sc.textFile("hdfs://path/to/textfile.txt")
// 指定分区数
val rdd2 = sc.textFile("hdfs://path/to/textfile.txt", 7)
1.3 从其他RDD进行转换创建RDD:可以使用RDD的转换操作(转换算子)(如map
、filter
、flatMap
等)
val sparkConf = new SparkConf().setAppName("RDD creation")
val sc = new SparkContext(sparkConf)
val input = sc.parallelize(List(1, 2, 3, 4, 5))
val rdd = input.map(x => x * 2)
RDD 不实际存储真正要计算的数据,而是记录了数据的位置在哪里,数据的转换关系(调用了什么方法,传入什么函数)。
RDD算子主要分为两类:
Driver
的 Action
动作时,这些转换才会真正运行。因为这样可以在 Action 时对 RDD 操作形成 DAG有向无环图进行 Stage 的划分和并行优化,这种设计让 Spark 更加有效率地运行转换算子(transformation):
map(func)
:对RDD中的每个元素应用func
函数,并返回一个新的RDDflatMap(func)
:对RDD中的每个元素应用func
函数,并返回一个扁平化的结果集合的新RDDfilter(func)
:根据func
函数的返回值为true或false,对RDD中的元素进行过滤,并返回一个新的RDDunion(other)
:返回一个包含两个RDD(当前RDD和other
RDD)所有元素的新RDDintersection(other)
:返回一个包含两个RDD(当前RDD和other
RDD)共有元素的新RDDsubtract(other)
:返回一个只包含当前RDD中存在而other
RDD中不存在元素的新RDDdistinct([numTasks])
:返回一个去重后的新RDDcartesian(other)
:返回一个包含两个RDD(当前RDD和other
RDD)所有元素组合的新RDDrepartition(numPartitions)
:重新分区,返回一个具有指定分区数的新RDDcoalesce(numPartitions)
:重新分区,减少 RDD 的分区数到指定值。在过滤大量数据之后,可以执行此操作partitionBy(partitioner)
:根据指定的分区器对RDD进行分区,并返回一个新RDD。sortBy(func, ascending=True, numPartitions=None)
:根据func
函数的返回值对RDD进行排序,并返回一个新的排序后的RDD。sample(withReplacement, fraction, seed)
:根据给定的采样比例fraction
,对RDD进行采样,并返回一个新的RDDglom()
:将每个分区的元素转换为数组,并返回一个新的RDD动作算子(action):
reduce(func)
:通过func
函数来减少RDD中的元素,并返回减少后的结果collect()
:以数组的形式返回RDD中的所有元素count()
:返回RDD中的元素数量first()
:返回RDD中的第一个元素take(n)
:返回RDD中的前n个元素max()
:返回RDD中的最大值min()
:返回RDD中的最小值sum()
:返回RDD中的所有元素的总和mean()
:返回RDD中的平均值variance()
:返回RDD中元素的方差stdev()
:返回RDD中元素的标准差foreach(func)
:对RDD中的每个元素应用func
函数foreachPartition(func)
:对RDD中的每个分区应用func
函数countByValue()
:针对(K,V)类型的 RDD,返回一个(V,Int)的 map,表示每一个 value 对应的元素个数countByKey()
:针对(K,V)类型的 RDD,返回一个(K,Int)的 map,表示每一个 key 对应的元素个数 某些 RDD
的计算或转换可能会比较耗费时间,如果这些 RDD
后续还会频繁的被使用到,那么可以将这些 RDD
进行持久化/缓存来提高RDD的重用和计算效率。持久化和缓存操作允许将RDD的数据存储在内存或磁盘上,从而避免重复计算和提高数据访问速度。
val rdd1 = sc.textFile("hdfs://node01:8020/words.txt")
val rdd2 = rdd1.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_)
rdd2.cache //缓存, 等同于rdd2.persist(StorageLevel.MEMORY_ONLY)
rdd2.sortBy(_._2,false).collect//触发action,会去读取HDFS的文件,rdd2会真正执行持久化
rdd2.sortBy(_._2,false).collect//触发action,会去读缓存中的数据,执行速度会比之前快,因为rdd2已经持久化到内存中了
3.1 persist(storageLevel: StorageLevel)
方法:将RDD标记为持久化,并可以指定数据存储级别Storage Level:
MEMORY_ONLY
:将RDD的数据存储在内存中MEMORY_AND_DISK
:如果内存不足时,将RDD的数据存储在内存中,其余数据溢出到磁盘(一般在开发中使用)MEMORY_ONLY_SER
:将RDD的数据以序列化的方式存储在内存中MEMORY_AND_DISK_SER
:类似于MEMORY_AND_DISK,
内存中放不下,则溢写到磁盘上,但以序列化方式存储数据。MEMORY_ONLY_2, MEMORY_AND_DISK_2等:将持久化数据存储为2份,备份每个分区存储在两个集群节点上
3.2 cache()
方法:是persist()
方法的一种简化形式,使用默认的存储级别MEMORY_ONLY
标记RDD为持久化
注意:
MEMORY_AND_DISK
或MEMORY_AND_DISK_SER
),以便在需要时从磁盘读取数据。unpersist()
方法可以手动将持久化的RDD从内存中移除3.3 checkpoint 持久化 (容错机制)
RDD的Checkpoint是一种在Spark中进行持久化的机制的容错机制,它可以将中间结果保存到可靠的分布式存储系统(如HDFS)上,以便在节点故障时可以快速恢复。
设置检查点:
val conf = new SparkConf()
.setAppName("CheckpointExample")
.set("spark.checkpoint.dir", "hdfs://path/to/checkpoints")
val sc = new SparkContext(conf)
val rdd = sc.parallelize(Seq(1, 2, 3, 4, 5))
rdd.checkpoint()
使用检查点恢复数据:
val conf = new SparkConf().setAppName("CheckpointRecovery").set("spark.checkpoint.dir", "hdfs://path/to/checkpoints")
val sc = new SparkContext(conf)
sc.setCheckpointDir("hdfs://path/to/checkpoints")
使用Checkpoint机制时需要注意以下几点:
相比于persist :
Spark SQL结合了传统的RDD API和SQL查询语言,使得开发人员可以使用SQL语句或Spark特有的DataFrame API进行数据操作和分析,从而更加方便和高效地处理结构化数据。
主要特性:
DataFrame和Dataset:Spark SQL引入了DataFrame和Dataset这两个抽象概念,它们提供了更高级的数据操作接口。
DataFrame是一种以RDD为基础的分布式数据集合,具有类似关系型数据库表的结构,可以进行筛选、聚合和SQL查询等操作。可以理解为JSON结构,只有列名和字段值类型限制。
Dataset则提供了更加类型安全的API,可以通过编译时校验来避免运行时错误。相比于DataFrame,DataSet可以理解为Entity,除了字段名和字段值类型限制,还有实体类型限制。
SQL查询和内置函数:Spark SQL支持使用标准的SQL查询语句对数据进行查询和聚合操作,允许开发人员利用熟悉的SQL语法进行数据分析。此外,Spark SQL还内置了许多常用的SQL函数(如聚合函数、日期函数等),以便更方便地对数据进行处理。
数据源扩展:Spark SQL支持各种数据源的连接和操作,包括关系型数据库(如MySQL、PostgreSQL)、Hive、Parquet、Avro、JSON、CSV等。借助Spark SQL的数据源接口,可以方便地读取、写入和处理不同格式的数据。
Catalyst优化器:Spark SQL通过Catalyst优化器对SQL查询进行优化,包括查询解析、逻辑优化、物理计划生成和执行优化等。Catalyst使用基于规则和成本的优化策略,能够自动转换和优化查询计划,提高查询性能。
Spark SQL 的 DSL风格示例:
# 打开Spark shell 控制台
sh bin/spark-shell
# 读取文件,创建RDD
val lineRDD = sc.textFile("file:///Users/zhoushimiao/Desktop/people.txt").map(_.split(" "))
# 定义Person 字段和字段类型
case class Person(id:Int, name:String, age:Int)
#将 RDD 和 Person 关联
val personRDD = lineRDD.map(x => Person(x(0).toInt, x(1), x(2).toInt)) //RDD[Person] , DataSet
# 将 RDD 转换成 DataFrame
val personDF = personRDD.toDF //DataFrame
# 查看数据
personDF.show
personDF.select(personDF.col("name")).show
personDF.select(personDF("name")).show
personDF.select(col("name")).show
personDF.select("name").show
也可以直接构建DataFrame:
val dataFrame=spark.read.text("file:///Users/zhoushimiao/Desktop/people.txt")
dataFrame.show //注意:直接读取的文本文件没有完整schema信息
dataFrame.printSchema
# 或者, 直接读取JSON文件, json中已经包含了schema信息
val jsonDF= spark.read.json("file:///Users/zhoushimiao/Desktop/people.json")
jsonDF.show
SQL 风格:
# 将DataFrame 转换成注册表
personDF.createOrReplaceTempView("t_person")
# 执行查询SQL
spark.sql("select id,name from t_person where id > 3").show
Spark SQL提供了两种不同的API,分别是SQL语法和DataFrame/Dataset API,也被称为Spark SQL DSL(Domain Specific Language):
Spark Streaming和Structured Streaming都是Apache Spark的流处理模块,用于处理实时数据流。
Spark Streaming 是一个基于 Spark Core 之上的实时计算框架,可以从很多数据源消费数据并对数据进行实时的处理,具有高吞吐量和容错能力强等特点。Spark Streaming使用DStreams (离散流), 可以使用高级函数和RDD操作进行数据处理。
Spark Streaming的特点:
容错性:Spark Streaming提供了容错机制,可以处理节点故障或数据丢失的情况。它使用Spark的弹性分布式数据集(RDDs)来恢复丢失的数据和故障的节点。
可扩展性:Spark Streaming可以与Spark的其他模块(如Spark SQL、MLlib)集成,从而实现更复杂的流处理和分析任务。它可以在大规模集群上运行,并根据数据量和负载自动扩展。
支持丰富的数据源:Spark Streaming支持多种数据源,包括Kafka、Flume、Hadoop HDFS、Apache NiFi等。这使得从多种来源获取流数据变得简单,并能够将数据处理结果发送到多个目标。
示例代码:
import org.apache.spark.streaming._
// 创建StreamingContext,批次时间间隔为5秒
val ssc = new StreamingContext(sparkConf, Seconds(5))
// 从TCP套接字获取流数据
val lines = ssc.socketTextStream("localhost", 9999)
// 对流数据进行处理
val words = lines.flatMap(_.split(" "))
val wordCounts = words.map(word => (word, 1)).reduceByKey(_ + _)
// 打印处理结果
wordCounts.print()
// 启动StreamingContext 和 等待流数据的到达和处理
ssc.start()
ssc.awaitTermination()
ssc.stop()
Structured Streaming基于DataFrame和Dataset API,这使得流处理代码和批处理代码之间的转换非常容易。开发者可以使用SQL查询、DataFrame的操作和内置函数来处理实时流数据,让开发者能够以类似于静态数据处理的方式进行流处理。
可以理解为,Structured Streaming 将数据源映射为类似于关系数据库中的表,然后将经过计算得到的结果映射为另一张表,完全以结构化的方式去操作流式数据,这种编程模型非常有利于处理分析结构化的实时数据;Spark Structured Streaming提供了更简化和统一的流处理编程模型,使得对实时流数据的处理变得更容易和直观。它支持更复杂的查询和操作,并提供了高级抽象和语义保证,以满足各种实时数据处理需求。
主要特点和优势:
基于事件时间(event time):Structured Streaming支持处理基于事件时间的数据,这意味着可以根据事件发生的真实时间处理数据,而不是根据数据到达的时间。这对于处理延迟和乱序事件数据非常有用。
Exactly-once语义保证:Structured Streaming提供了"恰好一次"(exactly-once)的语义保证,确保输出结果的一致性和可靠性,即每个输入的数据都会被精确处理一次,不会有丢失或重复数据。
高级抽象:Structured Streaming提供了高级的抽象,如窗口操作、流-静态数据连接、流-流连接等,使得处理更复杂的流处理场景更加简单和直观。
支持丰富的数据源和数据接收器:Structured Streaming支持多个数据源和数据接收器,如Kafka、Socket、File、HDFS等,并且可以将处理结果发送到外部存储系统(如Hadoop HDFS、Apache Cassandra)或集成其他模块(如Spark SQL)进行进一步分析。
示例代码:
// 导入必要的库
import org.apache.spark.sql.SparkSession
// 创建SparkSession, 也是一个Structured Streaming作业
val spark = SparkSession
.builder()
.appName("StructuredStreamingExample")
.getOrCreate()
// 从数据源创建输入DataFrame
val inputData = spark
.readStream
.format("socket")
.option("host", "localhost")
.option("port", 9999)
.load()
// 对输入数据进行转换和处理
val transformedData = inputData
.groupBy("word")
.count()
// 将结果DataFrame写入输出数据接收器
val query = transformedData
.writeStream
.format("console")
.outputMode("complete")
.start()
query.awaitTermination()
共同点:
区别:
此外, Structured Streaming提供了更简单和直观的编程模型,开发者可以通过类似静态表的API进行开发,减少了与批处理的编程范式之间的学习成本
Shuffle是指在数据的重新分区和重组过程中发生的数据洗牌操作。
在MapReduce 框架中, Shuffle 阶段是连结Map 和 Reduce 的桥梁, Map阶段通过Shuffle过程,将数据传输到Reduce。 由于Shuffle 涉及磁盘的读写和网络IO, 因此Shuffle 性能直接影响整个程序的性能。 Spark 也有Map和Reduce阶段, 因此也会出现shuffle。
Shuffle通常发生在以下情况
Shuffle 的过程分为三个阶段:
Spark中有三种主要的Shuffle实现方式:
SortShuffle(排序洗牌):
SortShuffle是一种基于排序的Shuffle实现方式,它将Map任务输出的数据先写入本地磁盘,然后Reducer任务再从Map任务所在的节点上拉取数据,并按键进行排序和合并。SortShuffle的优点是能够保证结果数据的全局有序性,适用于需要全局排序的场景,但可能会产生大量的磁盘写入和网络传输开销。
HashShuffle(哈希洗牌):
HashShuffle是一种基于哈希的Shuffle实现方式,它将Map任务输出的数据根据键的哈希值分组,将相同Key的数据发送到同一个Reducer任务上进行处理。HashShuffle具有良好的扩展性,因为数据在网络传输时是分散的,减少了磁盘写入和网络传输开销。但它不能保证结果的全局有序性。
TungstenSortShuffle(Tungsten排序洗牌):
TungstenSortShuffle是一种优化的Shuffle实现方式,通过使用Tungsten排序引擎来提高SortShuffle的性能。TungstenSortShuffle使用内存和磁盘结合的方式进行排序和合并,可以显著降低排序和合并过程中的磁盘读写开销,提高Shuffle的性能
Spark默认情况下会根据可用资源和配置参数自动选择适合的Shuffle实现方式。通常情况下,当可用内存较小时,会使用SortShuffle;而当系统具备足够的内存资源时,会使用TungstenSortShuffle来提高性能。
Shuffle性能优化方法:
调整并行度:通过调整分区数和并行度参数,可以减少数据移动和网络传输,从而提高Shuffle的性能。
提前合并:对于多个Shuffle操作,可以将它们合并在一起以减少Shuffle的次数。
使用本地性优化:通过使用数据本地性调度和预测性执行,将任务分配给尽可能靠近数据的节点,减少数据传输和读取的开销。
使用内存和磁盘结合:合理配置内存和磁盘使用比例,将适当的数据存储在内存中,减少对磁盘的读写操作。
使用压缩:对于大量数据的Shuffle操作,可以考虑使用数据压缩来减小网络传输和磁盘存储的开销。
Shuffle的过程是计算中非常昂贵的部分,因为它涉及数据的移动、排序和磁盘读写。因此,优化Shuffle操作对于提高Spark应用的性能和效率非常重要。