第2章 Spark核心概念回顾:RDD、DataFrame、Dataset、SparkSession、DAG调度器、内存计算原理

在深入回测加速实战之前,必须牢固掌握Spark的五大核心抽象与两大运行机制。本章将系统梳理RDD、DataFrame、Dataset、SparkSession、DAG调度器以及内存计算原理,为后续的并行回测优化打下坚实的理论基础。

2.1 核心数据抽象对比

Spark提供了三种主要的数据抽象:RDD、DataFrame和Dataset。理解它们的差异与适用场景,是选择正确回测数据结构的前提。

特性 RDD (Resilient Distributed Dataset) DataFrame Dataset
引入版本 Spark 1.0 Spark 1.3 Spark 1.6
数据表示 Java/Scala对象,无Schema Row对象,带Schema(列名+类型) 强类型JVM对象,带Schema
类型安全 编译时类型安全 运行时类型检查 编译时类型安全
性能优化 依赖开发者手动优化 Catalyst优化器自动优化 Catalyst + Tungsten编码优化
序列化方式 Java/Kryo序列化 列式二进制格式(Tungsten) Encoders编码(高效二进制)
适用场景 底层转换、非结构化数据 SQL分析、ETL、回测指标聚合 复杂业务逻辑、类型安全要求高
回测选型建议: 在回测场景中,DataFrame 是最常用的选择。它既能享受Catalyst优化器的自动谓词下推和列剪枝,又能通过SQL表达复杂的信号计算逻辑。当需要处理自定义策略对象且要求类型安全时,优先使用 Dataset。RDD通常只用于底层数据清洗或自定义分区器。

2.2 SparkSession:统一入口

SparkSession是Spark 2.0之后唯一的编程入口,整合了SQLContext、HiveContext和StreamingContext的功能。在回测应用中,通过SparkSession可以一站式完成数据读取、SQL查询、UDF注册和配置管理。

// 创建SparkSession,配置回测所需资源
val spark = SparkSession.builder()
  .appName("BacktestEngine")
  .config("spark.sql.shuffle.partitions", "200")   // 控制shuffle并行度
  .config("spark.memory.fraction", "0.6")          // 统一内存管理比例
  .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
  .getOrCreate()

// 注册回测所需的临时视图
val tradeDF = spark.read.parquet("hdfs://data/trades")
tradeDF.createOrReplaceTempView("trades")

// 执行回测SQL
val signalDF = spark.sql("""
  SELECT
    stock_code,
    trade_date,
    close_price,
    MA(close_price, 5) OVER (PARTITION BY stock_code ORDER BY trade_date) AS ma5
  FROM trades
""")

2.3 DAG调度器:回测任务的执行蓝图

DAG(有向无环图)调度器是Spark的调度核心。它将用户编写的转换操作(Transformation)构建成一个无环的Stage执行图,并决定哪些任务可以并行执行、哪些需要Shuffle。

关键机制: DAG调度器将作业划分为多个Stage,每个Stage包含一组可以流水线执行的窄依赖任务。宽依赖(Shuffle)会触发Stage边界,这也是回测中性能瓶颈的常见来源。
DAG调度器执行流程(回测场景) 原始行情数据 Stage 1: 过滤/映射 Shuffle: 分组聚合 Stage 2: 窗口函数 Stage 3: 信号计算 回测结果输出 Stage边界说明 • 窄依赖:map/filter/flatMap • 宽依赖:groupBy/reduceByKey • Shuffle是回测性能关键瓶颈

2.4 内存计算原理:回测加速的基石

Spark的内存计算能力是回测加速的核心。它通过统一内存管理(Unified Memory Management)将Executor内存划分为执行内存(Execution)和存储内存(Storage),两者可以动态抢占。

  • 执行内存: 用于Shuffle、Join、Sort、Aggregation等操作中的中间数据缓冲。回测中的窗口函数和分组聚合大量依赖此区域。
  • 存储内存: 用于缓存RDD/DataFrame/Dataset以及广播变量。回测中可以将历史行情表缓存到内存,避免反复读取HDFS。
  • 动态占用机制: 当执行内存不足时,可以抢占存储内存的空闲空间,反之亦然。但抢占可能导致缓存块被驱逐(Eviction),需合理设置 spark.memory.storageFraction。
回测内存调优警告: 如果回测涉及大量历史数据缓存(如全量日线行情),建议将 spark.memory.storageFraction 从默认0.5调高至0.6~0.7,避免执行阶段频繁驱逐缓存数据导致重复计算。
Spark统一内存管理架构 Executor JVM 堆内内存 (spark.memory.fraction = 0.6) 执行内存 (Execution) Shuffle 缓冲区 Join / Sort 中间结果 聚合操作哈希表 可抢占存储内存 存储内存 (Storage) 缓存 RDD / DataFrame 广播变量 缓存的历史行情表 可被执行内存抢占 预留内存 (spark.memory.offHeap.size + 300MB 安全区) 动态占用:执行内存不足时自动抢占存储内存空闲块,但可能触发缓存驱逐

2.5 核心概念在回测中的映射

将上述概念映射到回测场景,可以更直观地理解每个组件的作用:

Spark概念 回测中的对应角色 性能影响
RDD 原始行情数据的底层表示,用于自定义分区或容错恢复 序列化开销大,适合低频率操作
DataFrame 存储股票日线行情、财务指标、因子值,支持SQL信号计算 Catalyst优化可减少50%以上扫描量
Dataset 封装策略信号对象、交易指令,保证类型安全 Tungsten编码减少GC压力
SparkSession 回测引擎入口,管理Hive元数据、UDF、配置参数 统一配置,避免资源碎片
DAG调度器 将回测流程(读取→清洗→信号→聚合)划分为Stage 减少Shuffle次数直接提升回测速度
内存计算 缓存历史行情、广播股票池、中间结果复用 避免磁盘I/O,加速迭代回测

2.6 本章小结