Spark核心基础与架构全解析

Spark学习笔记

一、Spark核心基础

1.1 什么是Spark

Apache Spark 是一个快速、通用、可扩展的分布式计算引擎,基于Scala语言开发,由加州大学伯克利分校AMP实验室于2010年开源,2013年捐赠给Apache软件基金会,成为顶级开源项目。

核心定位:解决Hadoop MapReduce计算速度慢、迭代计算效率低的问题,提供内存计算能力,支持多种计算场景(批处理、流处理、机器学习、图计算等)。

1.2 Spark与Hadoop的对比

对比维度 Spark Hadoop MapReduce
计算模型 内存计算,中间结果可缓存,支持迭代计算 磁盘计算,中间结果写入磁盘,不支持迭代优化
计算速度 比MapReduce快10-100倍(内存计算优势) 速度较慢,受磁盘I/O限制
适用场景 批处理、流处理、机器学习、图计算、交互式查询 仅适用于大规模批处理,场景单一
依赖关系 可独立运行,也可依赖Hadoop的HDFS(存储)、YARN(调度) 依赖Hadoop生态(HDFS存储、YARN调度)

1.3 Spark核心优势

  • 快速:基于内存计算,减少磁盘I/O开销,迭代计算无需重复读写数据。

  • 通用:提供统一的API,支持批处理(Spark Core)、流处理(Spark Streaming/Structured Streaming)、机器学习(MLlib)、图计算(GraphX)四大核心模块。

  • 可扩展:支持水平扩展,可部署在单机、集群(Standalone、YARN、Mesos、K8s),节点数量可动态调整。

  • 易用:支持Scala、Java、Python、R、SQL等多种语言,API简洁,开发效率高。

1.4 Spark运行环境

1.4.1 运行模式分类

  • Local模式(本地模式):单机运行,适用于开发、测试、小规模数据处理(无需集群),核心参数为local[N](N为CPU核心数,local[*]表示使用全部核心)。

  • Standalone模式(独立集群模式):Spark自带的集群调度模式,无需依赖其他组件,部署简单,适用于中小规模集群。

  • YARN模式(Hadoop YARN模式):最常用的模式,依赖Hadoop的YARN作为资源调度器,HDFS作为存储,适用于大规模集群,与Hadoop生态无缝集成。

  • Mesos模式:依赖Apache Mesos作为资源调度器,适用于多框架(Spark、Hadoop、Storm等)共享集群资源的场景。

  • K8s模式(容器化模式):基于Kubernetes部署Spark集群,适用于容器化运维环境,可实现资源的动态调度和弹性伸缩。

1.4.2 核心运行环境配置(以YARN模式为例)

关键配置文件(spark/conf目录):

  • spark-env.sh:配置环境变量(如JAVA_HOME、HADOOP_HOME、YARN_CONF_DIR、Spark集群节点信息)。

  • spark-defaults.conf:配置Spark默认参数(如指定Master为yarn、指定Driver/Executor内存、指定日志存储路径)。

  • slaves(Standalone模式用):配置集群从节点(Worker)的主机名。

二、Spark核心组件与架构

2.1 Spark核心架构组件

Spark集群运行时,分为**Driver(驱动程序)Executor(执行器)**两大核心角色,配合Cluster Manager(集群管理器)实现资源调度和任务执行。

2.1.1 Driver(驱动节点)

核心职责:

  • 负责解析用户提交的代码(如Spark SQL、Scala/Java代码),生成抽象语法树(AST),转化为DAG(有向无环图)任务。

  • 对DAG任务进行划分,拆分为多个Stage(阶段),每个Stage包含多个Task(任务)。

  • 向Cluster Manager申请集群资源(CPU、内存),并将Task分配给Executor执行。

  • 监控Task的执行状态,处理Task失败重试、任务中断等异常情况。

  • 收集Executor执行后的结果,汇总并返回给用户。

2.1.2 Executor(执行节点)

核心职责:

  • 接收Driver分配的Task,执行具体的计算逻辑(如Map、Reduce、Filter等操作)。

  • 负责本地数据的存储和缓存(使用内存或磁盘缓存中间结果,提升迭代计算效率)。

  • 将Task执行的中间结果或最终结果反馈给Driver。

  • 每个Executor是一个独立的JVM进程,在集群的Worker节点上运行,可并行执行多个Task(Task数量由CPU核心数决定)。

2.1.3 Cluster Manager(集群管理器)

核心职责:负责集群资源(CPU、内存)的分配和管理,协调Driver和Executor之间的通信,不同运行模式对应不同的Cluster Manager:

  • Local模式:内置简单的Cluster Manager,无需单独部署。

  • Standalone模式:Spark自带的Cluster Manager(Master+Worker)。

  • YARN模式:Hadoop YARN的ResourceManager(资源管理)+ NodeManager(节点管理)。

  • K8s模式:Kubernetes的API Server、Scheduler等组件。

2.2 DAG(有向无环图)与Stage划分

2.2.1 DAG概念

Spark将用户提交的计算任务转化为DAG(有向无环图),其中每个节点代表一个RDD(弹性分布式数据集)的转换操作,边代表RDD之间的依赖关系(血缘关系),无循环依赖,确保任务可有序执行。

2.2.2 Stage划分规则

Driver会根据DAG中RDD的依赖关系,将任务划分为多个Stage(阶段),Stage之间是串行执行,Stage内部的Task是并行执行。

划分依据:宽依赖(Shuffle依赖)窄依赖(Narrow依赖)

  • 窄依赖:一个父RDD的分区只对应一个子RDD的分区(如Map、Filter、MapPartitions),无需Shuffle,可在同一个Stage内完成。

  • 宽依赖:一个父RDD的分区对应多个子RDD的分区(如GroupByKey、ReduceByKey、Join),需要进行Shuffle(数据重新分区、网络传输),宽依赖是Stage划分的边界,每个宽依赖对应一个新的Stage。

2.2.3 Shuffle(洗牌)机制

Shuffle是Spark中最耗时的操作,核心是将Executor之间的数据进行重新分区和网络传输,分为两个阶段:

  1. Map阶段:Executor执行Task,将计算结果按照指定的分区规则(Hash分区、Range分区等)写入本地磁盘的临时文件。

  2. Reduce阶段:每个Reduce Task从多个Map Task的临时文件中读取对应分区的数据,进行合并、计算,最终输出结果。

优化要点:减少Shuffle操作(如用ReduceByKey替代GroupByKey)、调整Shuffle分区数、开启Shuffle缓存等。

2.3 RDD(弹性分布式数据集)核心概念

2.3.1 RDD定义

RDD(Resilient Distributed Dataset)是Spark的核心抽象,代表一个不可变、可分区、可并行计算的分布式数据集,具备以下特性:

  • 不可变:RDD创建后无法修改,只能通过转换操作(Transformation)生成新的RDD。

  • 可分区:RDD的数据被划分为多个分区(Partition),分布在集群的不同Executor上,分区数量可配置(默认与HDFS块大小一致,128M/256M)。

  • 弹性:支持数据的容错(基于血缘关系自动恢复丢失的分区)、动态调整分区数量。

  • 可并行:每个分区的计算可独立并行执行,互不干扰。

2.3.2 RDD的创建方式

  1. 从本地集合创建(适用于Local模式测试):

    • parallelize():将本地集合转化为RDD,可指定分区数。

    • makeRDD():与parallelize()功能类似,支持指定分区的偏好位置(Preferred Locations)。

  2. 从外部存储创建(最常用):

    • textFile():读取文本文件(本地文件、HDFS、S3等),每行作为一个元素,返回RDD[String]。

    • read.csv()/read.json():通过Spark SQL的API读取结构化文件,返回DataFrame(可转换为RDD)。

    • hadoopFile():读取Hadoop支持的文件格式(如SequenceFile、MapFile)。

  3. 从其他RDD转换创建:通过Transformation操作(如Map、Filter)从已有的RDD生成新的RDD。

2.3.3 RDD的两大操作类型

Spark中RDD的操作分为Transformation(转换操作)Action(行动操作),核心区别:是否触发任务执行(惰性求值)。

(1)Transformation(转换操作)

特性:惰性求值,仅记录RDD之间的依赖关系(血缘关系),不立即执行计算,只有当Action操作被调用时,才会触发整个DAG的执行。

常用转换操作:

  • Map(func):对RDD中的每个元素应用func函数,返回一个新的RDD(一对一映射)。

  • Filter(func):对RDD中的每个元素应用func函数,筛选出返回值为true的元素,返回新的RDD。

  • FlatMap(func):对RDD中的每个元素应用func函数(返回一个迭代器),将迭代器中的元素扁平化,返回新的RDD(一对多映射)。

  • MapPartitions(func):对RDD的每个分区应用func函数(输入是整个分区的迭代器),效率比Map高(减少函数调用次数)。

  • GroupByKey():按照元素的key进行分组,返回(key, 迭代器)形式的RDD(宽依赖,有Shuffle)。

  • ReduceByKey(func):按照元素的key进行分组,对每个组内的元素应用func函数进行聚合(宽依赖,有Shuffle,比GroupByKey高效,因为会在本地先聚合)。

  • SortByKey(ascending=true):按照元素的key进行排序,ascending指定升序/降序。

  • Join(otherRDD, how="inner"):将两个RDD按照key进行连接(inner、left、right、full outer join),宽依赖,有Shuffle。

  • Union(otherRDD):将两个RDD合并,返回一个包含所有元素的新RDD(分区数相加,不去重)。

  • Intersection(otherRDD):求两个RDD的交集,返回新的RDD(去重,有Shuffle)。

(2)Action(行动操作)

特性:立即触发计算,执行整个DAG任务,将结果返回给Driver或写入外部存储。

常用行动操作:

  • count():返回RDD中元素的个数。

  • first():返回RDD中的第一个元素。

  • take(n):返回RDD中的前n个元素。

  • collect():将RDD中的所有元素收集到Driver端(注意:数据量不能太大,否则会导致Driver内存溢出)。

  • reduce(func):对RDD中的元素应用func函数进行聚合,返回一个单一值(如求和、求最大值)。

  • foreach(func):对RDD中的每个元素应用func函数,无返回值(如写入外部存储)。

  • saveAsTextFile(path):将RDD中的元素写入文本文件(本地路径、HDFS等),每个分区对应一个文件。

  • saveAsSequenceFile(path):将RDD中的元素写入SequenceFile(Hadoop支持的二进制文件格式)。

2.3.4 RDD的缓存与持久化

核心目的:当RDD需要被多次使用(如迭代计算、多次Action操作)时,将其缓存到内存或磁盘,避免重复计算,提升效率。

(1)缓存API
  • cache():默认将RDD缓存到内存(等价于persist(StorageLevel.MEMORY_ONLY))。

  • persist(storageLevel):手动指定存储级别,支持多种存储策略:

    • MEMORY_ONLY:仅缓存到内存,内存不足时,不缓存,下次使用重新计算(默认)。

    • MEMORY_AND_DISK:优先缓存到内存,内存不足时,将剩余数据缓存到磁盘。

    • DISK_ONLY:仅缓存到磁盘。

    • MEMORY_ONLY_SER:将RDD元素序列化后缓存到内存(节省内存,序列化有开销)。

    • MEMORY_AND_DISK_SER:序列化后缓存到内存和磁盘。

  • unpersist():清除RDD的缓存(释放内存/磁盘资源)。

(2)注意事项
  • 缓存是惰性的,只有当第一个Action操作触发时,才会将RDD缓存到指定存储介质。

  • 缓存的RDD会保留血缘关系,当缓存数据丢失(如内存溢出、节点故障)时,可通过血缘关系重新计算恢复。

  • 避免缓存不必要的RDD,否则会浪费资源;对于一次性使用的RDD,无需缓存。

2.3.5 RDD的血缘关系与容错机制

  1. 血缘关系(Lineage):RDD之间的依赖关系构成血缘关系,每个RDD都记录了自己的父RDD和转换操作,形成一个血缘链条。

  2. 容错机制:基于血缘关系实现容错,当某个RDD的分区丢失(如Executor节点故障)时,Spark无需重新计算整个RDD,只需根据血缘关系,重新计算丢失的分区即可,大大提升容错效率。

三、Spark SQL核心知识

3.1 Spark SQL简介

Spark SQL是Spark用于处理结构化数据的模块,基于Spark Core构建,支持SQL查询和DataFrame/Dataset API,可与Spark的其他模块(如Spark Streaming、MLlib)无缝集成。

核心优势:

  • 统一的API:支持SQL和DataFrame/Dataset两种方式操作结构化数据,灵活易用。

  • 优化的执行计划:内置Catalyst优化器,可对SQL语句和DataFrame操作进行优化(如谓词下推、列裁剪、Join优化),提升执行效率。

  • 兼容多种数据源:支持读取HDFS、Hive、MySQL、PostgreSQL、JSON、CSV等多种结构化数据源,也可写入多种存储。

  • 与Hive兼容:可直接访问Hive的元数据(Metastore),执行Hive SQL语句,无需修改现有Hive数据。

3.2 DataFrame与Dataset

3.2.1 DataFrame

DataFrame是一个分布式的表格型数据结构,包含行和列,每个列有名称和数据类型(Schema),类似于关系型数据库的表,也类似于Pandas的DataFrame,但分布式存储在集群中。

特点:

  • 有Schema(结构信息):明确每个列的名称和数据类型(如String、Int、Double),便于优化和类型检查。

  • 不可变:与RDD一样,DataFrame创建后无法修改,只能通过转换操作生成新的DataFrame。

  • 支持多种操作:可通过SQL语句操作,也可通过DataFrame API(如select、filter、groupBy)操作。

  • 底层基于RDD:DataFrame本质上是一个包含Schema信息的RDD[Row](Row表示一行数据)。

3.2.2 Dataset

Dataset是Spark 1.6版本引入的新抽象,结合了RDD的强类型 和DataFrame的结构化优势,支持编译时类型检查,比DataFrame更安全、更灵活。

特点:

  • 强类型:Dataset中的元素是特定的Java/Scala对象(如Case Class),编译时可检查类型错误,避免运行时类型异常。

  • 有Schema:与DataFrame一样,包含Schema信息,支持结构化查询。

  • 兼容RDD和DataFrame:可与RDD、DataFrame相互转换(Dataset <-> DataFrame <-> RDD)。

  • 支持两种API:Typed API(针对强类型对象的操作,如map、filter)和Untyped API(与DataFrame API一致,如select、groupBy)。

3.2.3 RDD、DataFrame、Dataset对比

对比维度 RDD DataFrame Dataset
类型安全 强类型(编译时检查) 弱类型(运行时检查) 强类型(编译时检查)
Schema信息 无Schema(仅存储数据) 有Schema(结构化) 有Schema(结构化)
优化支持 无优化(依赖用户手动优化) 支持Catalyst优化 支持Catalyst优化
API灵活性 高(支持任意复杂操作) 中(结构化操作为主) 高(结合RDD和DataFrame优势)
适用场景 非结构化/半结构化数据、复杂计算 结构化数据、SQL查询、快速分析 结构化数据、强类型需求、复杂业务逻辑

3.2.4 三者相互转换

  1. RDD → DataFrame:

    • 通过toDF()方法(需导入spark.implicits._),自动推断Schema(或手动指定Schema)。

    • 示例:rdd.toDF("name", "age")

  2. DataFrame → RDD:

    • 通过rdd属性,返回RDD[Row]。

    • 示例:df.rdd

  3. DataFrame → Dataset:

    • 通过as[CaseClass]方法,指定强类型对象。

    • 示例:df.as[Person](Person为Case Class)

  4. Dataset → DataFrame:

    • 通过toDF()方法,或使用selectExpr指定列名。

    • 示例:ds.toDF()

  5. Dataset → RDD:

    • 通过rdd属性,返回RDD[CaseClass]。

    • 示例:ds.rdd

  6. RDD → Dataset:

    • 先将RDD转换为DataFrame,再转换为Dataset;或直接通过toDS()方法(需导入spark.implicits._)。

    • 示例:rdd.map(x => Person(x._1, x._2)).toDS()

3.3 Spark SQL实操要点

3.3.1 SparkSession(入口对象)

Spark SQL的入口对象是SparkSession,替代了Spark 1.x版本中的SQLContext和HiveContext,负责创建DataFrame、执行SQL查询、访问数据源等。

创建SparkSession示例(Scala):

scala 复制代码
import org.apache.spark.sql.SparkSession

object SparkSQLDemo {
  def main(args: Array[String]): Unit = {
    // 创建SparkSession
    val spark = SparkSession.builder()
      .appName("SparkSQLDemo")  // 应用名称
      .master("local[*]")       // 运行模式(Local模式)
      .enableHiveSupport()      // 开启Hive支持(可选)
      .getOrCreate()            // 获取或创建SparkSession
    
    // 导入隐式转换(用于RDD→DataFrame等操作)
    import spark.implicits._
    
    // 执行SQL查询、操作DataFrame/Dataset...
    
    // 关闭SparkSession
    spark.stop()
  }
}

3.3.2 读取结构化数据源

Spark SQL支持读取多种结构化数据源,常用API:

  • 读取CSV文件:spark.read.csv("path").toDF("col1", "col2"),可指定分隔符、是否有表头(header=true)、数据类型(inferSchema=true)。

  • 读取JSON文件:spark.read.json("path"),自动推断Schema。

  • 读取Hive表:spark.sql("select * from hive_db.hive_table"),需开启enableHiveSupport()。

  • 读取JDBC数据库(MySQL/PostgreSQL):
    spark.read.format("jdbc") .option("url", "jdbc:mysql://host:port/dbname") .option("dbtable", "table_name") .option("user", "username") .option("password", "password") .load()

3.3.3 执行SQL查询

Spark SQL支持两种SQL查询方式:

  1. 直接执行SQL语句:通过spark.sql("sql语句"),返回DataFrame。

    • 示例:val df = spark.sql("select name, age from user where age > 18")
  2. 创建临时视图(Temp View)后查询:

    • createTempView("view_name"):创建会话级临时视图,仅当前SparkSession有效,会话关闭后视图消失。

    • createGlobalTempView("view_name"):创建全局临时视图,所有SparkSession共享,查询时需加global_temp前缀(如select * from global_temp.view_name)。

    • 示例:df.createTempView("user_view"); spark.sql("select * from user_view")

3.3.4 写入结构化数据源

常用写入API:df.write.format("格式").option("参数").save("路径")

  • 写入CSV/JSON文件:df.write.csv("path")、df.write.json("path"),可指定mode(overwrite覆盖、append追加、ignore忽略、errorifexists报错)。

  • 写入Hive表:df.write.mode("overwrite").saveAsTable("hive_db.hive_table")。

  • 写入JDBC数据库:df.write.format("jdbc") .option("url", "jdbc:mysql://host:port/dbname") .option("dbtable", "table_name") .option("user", "username") .option("password", "password") .mode("append") .save()

四、Spark Streaming核心知识

4.1 Spark Streaming简介

Spark Streaming是Spark用于处理实时流数据的模块,基于Spark Core构建,采用"微批处理"(Micro-Batching)机制,将实时流数据切分为一系列小的批处理任务,批量处理数据,实现近实时计算(延迟通常在秒级)。

核心优势:

  • 易用性:API与Spark Core/RDD高度一致,熟悉RDD操作即可快速上手。

  • 容错性:基于RDD的血缘关系实现容错,流数据的每个微批都对应一个RDD,故障时可重新计算。

  • 可扩展性:与Spark集群无缝集成,支持水平扩展,可处理大规模流数据。

  • 集成性:可与Spark SQL、MLlib、GraphX等模块集成,实现流数据的实时分析、机器学习等场景。

注意:Spark Streaming是"微批处理",并非真正的实时流处理(如Flink的流式处理),适用于延迟要求不高(秒级)的场景;若需要毫秒级延迟,建议使用Flink。

4.2 Spark Streaming核心概念

4.2.1 DStream(离散流)

DStream(Discretized Stream)是Spark Streaming的核心抽象,代表一个连续的流数据序列,本质上是一系列连续的RDD(每个RDD对应一个微批的数据)。

特性:

  • 不可变:与RDD一样,DStream创建后无法修改,只能通过转换操作生成新的DStream。

  • 微批构成:每个DStream由多个微批的RDD组成,每个RDD包含一个时间间隔(Batch Interval)内的数据。

  • 操作类型:支持转换操作(Transformation)和输出操作(Output Operation),与RDD的操作类似。

4.2.2 Batch Interval(批处理间隔)

核心参数,指定将流数据切分为微批的时间间隔(如1秒、5秒),决定了Spark Streaming的延迟时间(延迟 ≈ 批处理间隔)。

注意:批处理间隔的设置需结合集群性能和业务需求,间隔太小(如100ms)会导致任务过多,集群压力大;间隔太大(如1分钟)会导致延迟过高。

4.2.3 StreamingContext(流处理入口)

StreamingContext是Spark Streaming的入口对象,负责创建DStream、启动流处理、停止流处理,基于SparkContext创建。

创建StreamingContext示例(Scala):

scala 复制代码
import org.apache.spark.streaming.{StreamingContext, Seconds}
import org.apache.spark.SparkConf

object SparkStreamingDemo {
  def main(args: Array[String]): Unit = {
    // 1. 创建SparkConf
    val conf = new SparkConf().setAppName("SparkStreamingDemo").setMaster("local[*]")
    
    // 2. 创建StreamingContext,指定批处理间隔为1秒
    val ssc = new StreamingContext(conf, Seconds(1))
    
    // 3. 创建DStream、执行操作...
    
    // 4. 启动流处理(开始接收数据并处理)
    ssc.start()
    
    // 5. 等待流处理停止(阻塞当前线程,直到手动停止或发生异常)
    ssc.awaitTermination()
    
    // 6. 停止流处理(可选,手动停止)
    ssc.stop()
  }
}

4.3 DStream的操作

4.3.1 转换操作(Transformation)

与RDD的转换操作类似,分为无状态转换有状态转换

(1)无状态转换

每个微批的处理独立于其他微批,不依赖历史数据,常用操作:

  • map(func):对DStream中的每个元素应用func函数,返回新的DStream。

  • filter(func):筛选出符合条件的元素,返回新的DStream。

  • flatMap(func):扁平化处理,与RDD的flatMap一致。

  • reduceByKey(func):按照key聚合,每个微批内独立聚合(不依赖历史微批的数据)。

  • join(otherDStream):将两个DStream按照key连接,每个微批内独立连接。

(2)有状态转换

处理当前微批时,依赖历史微批的数据(如累计计数、滑动窗口统计),常用操作:

  • updateStateByKey(func):维护每个key的状态,根据当前微批的数据和历史状态,更新key的新状态(如累计求和、计数)。

  • window(windowDuration, slideDuration):创建滑动窗口DStream,窗口时长(windowDuration)指定窗口包含的微批数量,滑动步长(slideDuration)指定窗口滑动的微批数量(如窗口时长10秒,滑动步长5秒,即每5秒统计一次过去10秒的数据)。

  • reduceByKeyAndWindow(func, windowDuration, slideDuration):在滑动窗口内,按照key聚合数据。

注意:使用有状态转换时,需设置检查点(Checkpoint),用于保存历史状态数据,防止节点故障导致状态丢失。

4.3.2 输出操作(Output Operation)

触发DStream的计算,将处理结果输出到外部存储或控制台,常用操作:

  • print():将每个微批的前10条数据打印到控制台(适用于测试)。

  • foreachRDD(func):对DStream中的每个RDD应用func函数,可将数据写入外部存储(如HDFS、MySQL、Kafka)。

  • saveAsTextFiles(prefix, suffix):将每个微批的RDD写入文本文件,文件名由前缀、时间戳、后缀组成。

4.4 数据源接入

Spark Streaming支持接入多种实时数据源,分为基础数据源高级数据源

4.4.1 基础数据源

  • 文件数据源:监控指定目录,当有新文件写入时,读取文件数据作为微批(支持HDFS、本地文件等),示例:ssc.textFileStream("hdfs://path")。

  • Socket数据源:从指定的Socket端口接收数据(适用于测试),示例:ssc.socketTextStream("host", port)。

  • RDD队列数据源:将本地RDD队列作为数据源,适用于测试,示例:ssc.queueStream(rddQueue)。

4.4.2 高级数据源

需导入对应的依赖包(如Kafka、Flume),常用:

  • Kafka数据源(最常用):通过Spark Streaming的Kafka集成包,接收Kafka主题中的数据,支持两种集成方式:Receiver-based(旧版本,效率低)和Direct Stream(新版本,效率高,推荐)。

  • Flume数据源:接收Flume采集的日志数据,支持推模式(Flume push to Spark)和拉模式(Spark pull from Flume)。

  • Kinesis数据源:接收Amazon Kinesis的流数据。

4.5 检查点(Checkpoint)机制

4.5.1 核心作用

  • 保存状态数据:用于有状态转换(如updateStateByKey、window),保存历史状态,防止节点故障导致状态丢失。

  • 恢复Driver:当Driver节点故障时,可从检查点中恢复Driver的状态,重启流处理任务,无需重新启动。

4.5.2 配置检查点

通过StreamingContext的checkpoint()方法指定检查点存储路径(本地路径或HDFS路径),示例:

scala 复制代码
// 设置检查点路径(HDFS路径,推荐)
ssc.checkpoint("hdfs://path/to/checkpoint")

注意:检查点路径需是可靠的存储(如HDFS),本地路径仅适用于Local模式测试,集群模式下会导致节点故障后无法恢复。

五、Spark常见问题与优化

5.1 常见问题及解决方案

5.1.1 Driver内存溢出(OOM)

原因:Driver内存不足,常见于collect()操作读取大量数据、广播变量过大、任务过多导致Driver负载过高。

解决方案:

  • 避免使用collect()操作读取大量数据,改用take()、saveAsTextFile()等操作。

  • 调整Driver内存:通过--driver-memory参数指定(如spark-submit --driver-memory 4g)。

  • 优化广播变量:减少广播变量的大小,避免广播大量数据。

5.1.2 Executor内存溢出(OOM)

原因:Executor内存不足,常见于缓存大量RDD、Shuffle数据量过大、单个Task处理的数据过多。

解决方案:

  • 调整Executor内存:通过--executor-memory参数指定(如spark-submit --executor-memory 8g)。

  • 优化缓存策略:减少不必要的RDD缓存,使用MEMORY_AND_DISK存储级别,避免仅缓存到内存。

  • 调整Shuffle分区数:增加Shuffle分区数(--conf spark.sql.shuffle.partitions=200),减少单个分区的数据量。

  • 优化Task粒度:增加Task数量,使每个Task处理的数据量适中(建议每个Task处理128M-256M数据)。

5.1.3 Shuffle性能低下

原因:Shuffle操作涉及网络传输和磁盘I/O,是Spark中最耗时的操作,常见于分区数不合理、数据倾斜、序列化效率低。

解决方案:

  • 调整Shuffle分区数:根据集群CPU核心数和数据量调整,一般设置为CPU核心数的2-3倍(如100-200个分区)。

  • 避免不必要的Shuffle:用ReduceByKey替代GroupByKey,用Broadcast Join替代普通Join(小表Join大表时)。

  • 解决数据倾斜:

    • 对倾斜的key进行拆分(如加盐),分散到多个分区。

    • 使用随机前缀,将倾斜的key分散到不同的Reduce Task。

    • 过滤掉异常倾斜的key(如无效数据)。

  • 优化序列化:使用Kyro序列化替代Java序列化(--conf spark.serializer=org.apache.spark.serializer.KryoSerializer),减少数据传输量和内存占用。

5.1.4 任务执行缓慢

原因:CPU核心不足、内存分配不合理、数据倾斜、Task数量过少。

解决方案:

  • 增加集群节点或CPU核心数,提升并行计算能力。

  • 合理分配内存:区分Executor的堆内存和堆外内存,避免内存浪费。

  • 解决数据倾斜(参考上面的Shuffle优化)。

  • 增加Task数量,提升并行度(Task数量建议大于等于集群总CPU核心数)。

5.2 核心优化策略

5.2.1 资源优化

  • Driver优化:合理设置Driver内存(根据任务复杂度),避免Driver成为瓶颈。

  • Executor优化:

    • 设置合适的Executor数量(--num-executors),一般为集群节点数的2-3倍。

    • 设置合适的Executor内存(--executor-memory)和CPU核心数(--executor-cores),避免单Executor占用过多资源。

  • 内存优化:开启堆外内存(--conf spark.executor.memoryOverhead),用于存储序列化数据、Shuffle临时数据,避免堆内存溢出。

5.2.2 计算优化

  • 减少Shuffle操作:优先使用窄依赖操作(如Map、Filter),避免不必要的宽依赖(如GroupByKey、Join),从源头减少网络传输和磁盘I/O开销,这是计算优化的核心前提(与5.1.3 Shuffle性能低下的优化逻辑呼应)。

  • 优化Task粒度:合理设置Task数量,一般建议Task数量为集群总CPU核心数的2-3倍,确保每个Task处理的数据量控制在128M-256M之间。既避免Task过大导致单个Executor内存压力激增,也防止Task过小造成集群任务调度开销过高、资源利用率不足。

  • 使用高效的算子:优先选择高性能算子替代低效算子,降低计算开销。例如用ReduceByKey替代GroupByKey(ReduceByKey支持本地预聚合,可大幅减少Shuffle阶段的数据量);简单筛选后映射场景,可用filter+map替代flatMap提升效率;去重操作可通过groupByKey+first()自定义实现,减少distinct算子的冗余计算。

  • 优化数据格式:采用高效存储和序列化格式,进一步降低I/O和内存消耗。存储层面,用Parquet、ORC等列式存储格式替代文本格式,可减少不必要的字段读取,提升磁盘读取效率;序列化层面,推荐使用Kyro序列化替代默认的Java序列化,序列化效率可提升30%-50%,同时大幅减少内存占用和网络传输的数据量。

  • 避免数据倾斜:从计算层面进一步规避倾斜问题。可通过数据预处理过滤无效倾斜key、大表与大表Join时采用分桶Join减少Shuffle压力、根据数据实时分布动态调整分区数等方式,确保计算任务均匀分配到各个Executor,避免单个Task长期阻塞拖慢整体任务进度。

  • 利用惰性求值特性:充分发挥Spark惰性求值的优势,合理规划Transformation和Action操作的执行顺序。将过滤、列裁剪等轻量级操作前置,提前筛选无效数据、剔除不必要字段,减少后续计算的数据量;同时避免频繁调用Action操作,尽量将多个关联的Action操作合并执行,减少DAG的重复构建和计算,提升整体执行效率。

相关推荐
happyboy19862111 小时前
2026 大专大数据专业报考条件是什么?
大数据
Traced back1 小时前
【.NET7 WinForm 实战】三层架构+EF Core+多数据库+完整功能(源码+教程+脚本)
数据库·架构·.net
创客匠人老蒋2 小时前
创客匠人:2026知识付费“生死局”,AI智能体如何重构“交付”价值?
大数据·人工智能·重构
james的分享2 小时前
大数据领域核心 SQL 优化框架Apache Calcite介绍
大数据·sql·apache·calcite
绝无仅有3 小时前
Java多线程并发问题解决方案全解析
后端·面试·架构
沃彼特3 小时前
警告:固态硬盘长期不通电=数据报废!你的SSD多久没开机了?
大数据
够快云库3 小时前
制造业非结构化数据治理:架构解析与实战复盘
大数据·人工智能·架构·企业文件安全
Fox爱分享3 小时前
字节三面:千万级订单对账,怎么保证“一分钱不错”?答不出“流式比对+缓冲池”,基本就挂了
面试·程序员·架构