01-Spark基础概念

248 阅读4分钟

Spark组成节点:

  • 驱动器进程,Spark集群中的一个节点上,3个功能:
    • 维护Spark程序的有关信息
    • 与用户交互
    • 给执行器分配任务
  • 执行器进程,Spark集群的节点,2个功能:
    • 执行驱动器分配的任务

    • 向驱动器报告状态

image.png

用户通过SparkSession和驱动器进程交互。

集群管理器一般3种:

  • Spark自带集群管理器
  • YARN
  • K8S

Spark可以有本地模式,本地模式的情况下,驱动器进程和执行进程,都会成为一个Spark本地进程中的不同线程

DataFrame 行和列的数据表,类似电子表格,或者Python的Pandas的结构。列和列的属性构成了模式。Spark的DataFrame可以跨越多个计算机,是分布式的。

image.png

数据分区 分区是指Spark集群中,位于一台物理机上的多行数据的集合。Spark进程工作的时候,一个数据分区上,同时至多有一个执行器执行计算。一般来说,使用DataFrame的时 候,该结构会自动处理分区。

转换操作和执行操作 Spark的核心数据结构都是不可变的。我们通过转换操作来定义一种结构向另一种结构的转换,转换操作定义完成后,不会立刻执行,而是有Spark的分析器解析组合出一系列的执行步骤,需要执行操作来触发整个执行流程。

转换操作分为两种:

  • 窄依赖关系转换:每个输入分区仅决定一个输入分区的转换,比如map
  • 宽依赖关系转换:每个输入分区决定了多个输出分区,一般成为shuffle操作,比如sort

spark.drawio.png map转化前后,没有涉及到不同分区交互的操作,而是原来分区的数据直接映射。

spark.drawio.png 排序前后,不同的分区之间会有交换操作

执行操作,一般有takecount等,需要实际获取数据的操作。

给出一个代码示例:

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的方式是等价的,我们介绍下对应的流程。

  1. 数据分组
  2. 对每组分组后的数据,累加求和
  3. 重命名计数列
  4. 对重命名的列执行降序
  5. 选出5个数据

值得一提的是,scala方式中,我们直接sort了一个desc的结果,像这类desc的方法,输入的列名,返回的是一个column,其实这里只是表达一个对该列降序的方式,或者说这是一个表达式,列和表达式是等价的