Spark组成节点:
- 驱动器进程,Spark集群中的一个节点上,3个功能:
- 维护Spark程序的有关信息
- 与用户交互
- 给执行器分配任务
- 执行器进程,Spark集群的节点,2个功能:
-
执行驱动器分配的任务
-
向驱动器报告状态
-
用户通过SparkSession和驱动器进程交互。
集群管理器一般3种:
- Spark自带集群管理器
- YARN
- K8S
Spark可以有本地模式,本地模式的情况下,驱动器进程和执行进程,都会成为一个Spark本地进程中的不同线程
DataFrame
行和列的数据表,类似电子表格,或者Python的Pandas的结构。列和列的属性构成了模式。Spark的DataFrame可以跨越多个计算机,是分布式的。
数据分区
分区是指Spark集群中,位于一台物理机上的多行数据的集合。Spark进程工作的时候,一个数据分区上,同时至多有一个执行器执行计算。一般来说,使用DataFrame的时
候,该结构会自动处理分区。
转换操作和执行操作
Spark的核心数据结构都是不可变的。我们通过转换操作来定义一种结构向另一种结构的转换,转换操作定义完成后,不会立刻执行,而是有Spark的分析器解析组合出一系列的执行步骤,需要执行操作来触发整个执行流程。
转换操作分为两种:
- 窄依赖关系转换:每个输入分区仅决定一个输入分区的转换,比如
map等 - 宽依赖关系转换:每个输入分区决定了多个输出分区,一般成为
shuffle操作,比如sort等
map转化前后,没有涉及到不同分区交互的操作,而是原来分区的数据直接映射。
排序前后,不同的分区之间会有交换操作
执行操作,一般有take和count等,需要实际获取数据的操作。
给出一个代码示例:
package example
import org.apache.spark.sql.SparkSession
object SparkExample {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder.appName("SparkExample")
.master("local").getOrCreate // 创建SparkSession
spark.conf.set("spark.sqpl.shuflle.partitions", "5") // 转换操作设置输出分区为5
spark.sparkContext.setLogLevel("ERROR") // 只输出ERROR日志
val flightData = spark
.read
.option("inferSchema", "true")
.option("header", "true")
.csv("/Users/ericklv/Documents/code/Spark-The-Definitive-Guide-master/data/flight-data/csv/2015-summary.csv")
// 获取前3个
val arr3 = flightData.take(3)
println(arr3.mkString(","))
// 获取排序后的2个
val arr2 = flightData.sort("count").take(2)
println(arr2.mkString(","))
// 把DataFrame放入数据表或者视图中,可以使用SparkSQL执行查询
flightData.createOrReplaceTempView("flight_data_2015")
// 注意SQL语句中,flight_data_2015是个表了. count(1)表示选择非空的
val sqlWay = spark.sql(
"""
|SELECT DEST_COUNTRY_NAME, count(1)
| FROM flight_data_2015
|GROUP BY DEST_COUNTRY_NAME
|""".stripMargin)
val sqlCnt = sqlWay.count()
println(sqlCnt)
// 通过DataFrame的方式
val dfCnt = flightData.groupBy("DEST_COUNTRY_NAME").count().count()
println(dfCnt)
}
}
输出数据:
[United States,Romania,15],[United States,Croatia,1],[United States,Ireland,344]
[United States,Singapore,1],[Moldova,United States,1]
132
132
当然,我们也可以执行一些复杂的操作,比如直接对DataFrame返回的列执行操作,举个例子:
package example
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions.desc
object SparkExample {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder.appName("SparkExample")
.master("local").getOrCreate // 创建SparkSession
spark.conf.set("spark.sqpl.shuflle.partitions", "5") // 转换操作设置输出分区为5
spark.sparkContext.setLogLevel("ERROR") // 只输出ERROR日志
val flightData = spark
.read
.option("inferSchema", "true")
.option("header", "true")
.csv("/Users/ericklv/Documents/code/Spark-The-Definitive-Guide-master/data/flight-data/csv/2015-summary.csv")
flightData.createOrReplaceTempView("flight_data_2015")
val maxSql = spark.sql(
"""
|SELECT DEST_COUNTRY_NAME, sum(count) as destination_total
| FROM flight_data_2015
|GROUP BY DEST_COUNTRY_NAME
|ORDER BY sum(count) DESC
|LIMIT 5
|""".stripMargin)
maxSql.explain() // 输出解析过程
println("===============================")
maxSql.show(5) // 输出最多5条数据
println("===============================")
val dfData = flightData
.groupBy("DEST_COUNTRY_NAME")
.sum("count")
.withColumnRenamed("sum(count)", "destination_total")
.sort(desc("destination_total"))
.limit(5)
dfData.explain()
println("===============================")
dfData.show(5)
}
}
输出:
== Physical Plan ==
AdaptiveSparkPlan isFinalPlan=false
+- TakeOrderedAndProject(limit=5, orderBy=[destination_total#22L DESC NULLS LAST], output=[DEST_COUNTRY_NAME#16,destination_total#22L])
+- HashAggregate(keys=[DEST_COUNTRY_NAME#16], functions=[sum(count#18)])
+- Exchange hashpartitioning(DEST_COUNTRY_NAME#16, 200), ENSURE_REQUIREMENTS, [id=#36]
+- HashAggregate(keys=[DEST_COUNTRY_NAME#16], functions=[partial_sum(count#18)])
+- FileScan csv [DEST_COUNTRY_NAME#16,count#18] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/ericklv/Documents/code/Spark-The-Definitive-Guide-master/d..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct<DEST_COUNTRY_NAME:string,count:int>
===============================
+-----------------+-----------------+
|DEST_COUNTRY_NAME|destination_total|
+-----------------+-----------------+
| United States| 411352|
| Canada| 8399|
| Mexico| 7140|
| United Kingdom| 2025|
| Japan| 1548|
+-----------------+-----------------+
===============================
== Physical Plan ==
AdaptiveSparkPlan isFinalPlan=false
+- TakeOrderedAndProject(limit=5, orderBy=[destination_total#49L DESC NULLS LAST], output=[DEST_COUNTRY_NAME#16,destination_total#49L])
+- HashAggregate(keys=[DEST_COUNTRY_NAME#16], functions=[sum(count#18)])
+- Exchange hashpartitioning(DEST_COUNTRY_NAME#16, 200), ENSURE_REQUIREMENTS, [id=#113]
+- HashAggregate(keys=[DEST_COUNTRY_NAME#16], functions=[partial_sum(count#18)])
+- FileScan csv [DEST_COUNTRY_NAME#16,count#18] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/ericklv/Documents/code/Spark-The-Definitive-Guide-master/d..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct<DEST_COUNTRY_NAME:string,count:int>
===============================
+-----------------+-----------------+
|DEST_COUNTRY_NAME|destination_total|
+-----------------+-----------------+
| United States| 411352|
| Canada| 8399|
| Mexico| 7140|
| United Kingdom| 2025|
| Japan| 1548|
+-----------------+-----------------+
SQL和DataFrame的方式是等价的,我们介绍下对应的流程。
- 数据分组
- 对每组分组后的数据,累加求和
- 重命名计数列
- 对重命名的列执行降序
- 选出5个数据
值得一提的是,scala方式中,我们直接sort了一个desc的结果,像这类desc的方法,输入的列名,返回的是一个column,其实这里只是表达一个对该列降序的方式,或者说这是一个表达式,列和表达式是等价的