3. Spark 基础回顾:核心概念与编程模型
在将 Backtrader 回测引擎与 Spark 集成之前,必须牢固掌握 Spark 的核心抽象与编程模型。本章从 RDD、DataFrame、Dataset 三大数据抽象出发,逐步深入到 Spark SQL 与 Structured Streaming,为后续的分布式回测引擎设计奠定基础。
3.1 三大核心数据抽象
Spark 提供了三种主要的数据抽象:RDD(弹性分布式数据集)、DataFrame 和 Dataset。它们分别面向不同的使用场景,在性能、类型安全和易用性上各有侧重。
| 特性 | RDD | DataFrame | Dataset |
|---|---|---|---|
| 引入版本 | Spark 1.0 | Spark 1.3 | Spark 1.6 |
| 类型安全 | 强类型(编译时) | 弱类型(运行时) | 强类型(编译时) |
| 数据表示 | Java/Python 对象 | Row 对象 + Schema | JVM 类型对象 |
| 优化引擎 | 无(用户手动优化) | Catalyst 优化器 | Catalyst + Tungsten |
| 适用场景 | 底层转换、非结构化数据 | SQL 分析、ETL | 类型安全 + 高性能 |
| Python 支持 | 完整 | 完整 | 有限(PySpark 中为 DataFrame) |
3.2 RDD:弹性分布式数据集
RDD 是 Spark 最基础的数据抽象,代表一个不可变、可分区、可并行计算的集合。RDD 的“弹性”体现在:当部分分区丢失时,可以通过 lineage(血统)重新计算。
3.2.1 RDD 的创建与操作
# 从集合创建
data = [("AAPL", 150.0), ("GOOG", 2800.0), ("MSFT", 300.0)]
rdd = spark.sparkContext.parallelize(data)
# 从文件创建
rdd_text = spark.sparkContext.textFile("hdfs://path/to/prices.csv")
# 转换操作(lazy)
filtered_rdd = rdd.filter(lambda x: x[1] > 200.0)
mapped_rdd = filtered_rdd.map(lambda x: (x[0], x[1] * 1.1))
# 行动操作(触发计算)
result = mapped_rdd.collect()
print(result) # [('GOOG', 3080.0), ('MSFT', 330.0)]
3.2.2 RDD 的依赖关系
RDD 之间的依赖分为窄依赖(Narrow Dependency)和宽依赖(Wide Dependency),这决定了任务调度的效率与容错策略。
宽依赖:每个父 RDD 分区可能被子 RDD 的多个分区使用(如 groupByKey、reduceByKey),会产生 Shuffle。
3.3 DataFrame 与 Dataset
DataFrame 是带 Schema 的分布式 Row 集合,类似于关系数据库中的表。Dataset 是 DataFrame 的类型安全版本,在 Scala/Java 中提供编译时类型检查。
3.3.1 DataFrame 的创建
# 从 SparkSession 创建
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("BacktraderSpark").getOrCreate()
# 从 RDD 转换
rdd = spark.sparkContext.parallelize([(1, "AAPL", 150.0), (2, "GOOG", 2800.0)])
df = rdd.toDF(["id", "symbol", "price"])
# 从 CSV 文件读取
df_prices = spark.read \
.option("header", "true") \
.option("inferSchema", "true") \
.csv("hdfs://path/to/historical_prices.csv")
# 从 Parquet 读取(推荐用于回测数据)
df_parquet = spark.read.parquet("hdfs://path/to/backtest_data.parquet")
3.3.2 DataFrame 的典型操作
# 查看 Schema
df_prices.printSchema()
# 选择与过滤
df_filtered = df_prices.select("symbol", "close_price") \
.filter(df_prices.symbol == "AAPL")
# 聚合操作
df_avg = df_prices.groupBy("symbol") \
.agg({"close_price": "avg", "volume": "sum"}) \
.withColumnRenamed("avg(close_price)", "avg_price")
# 窗口函数(计算移动平均)
from pyspark.sql.window import Window
from pyspark.sql.functions import avg
window_spec = Window.partitionBy("symbol") \
.orderBy("date") \
.rowsBetween(-20, 0)
df_ma = df_prices.withColumn("ma_21", avg("close_price").over(window_spec))
3.4 SparkSession:统一入口
SparkSession 是 Spark 2.0 引入的统一入口点,替代了旧的 SparkContext、SQLContext、HiveContext。在 Backtrader 与 Spark 集成中,所有操作都从 SparkSession 开始。
# 创建 SparkSession(推荐配置)
spark = SparkSession.builder \
.appName("BacktraderSparkIntegration") \
.config("spark.sql.shuffle.partitions", "200") \
.config("spark.sql.adaptive.enabled", "true") \
.config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \
.getOrCreate()
# 获取 SparkContext(底层 API)
sc = spark.sparkContext
# 设置日志级别
spark.sparkContext.setLogLevel("WARN")
3.5 Spark SQL:结构化查询
Spark SQL 允许使用 SQL 语句直接查询 DataFrame,并自动应用 Catalyst 优化器。对于熟悉 SQL 的量化分析师,这是最直观的数据处理方式。
3.5.1 临时视图与 SQL 查询
# 注册临时视图
df_prices.createOrReplaceTempView("prices")
# 使用 SQL 查询
df_result = spark.sql("""
SELECT
symbol,
date,
close_price,
AVG(close_price) OVER (
PARTITION BY symbol
ORDER BY date
ROWS BETWEEN 20 PRECEDING AND CURRENT ROW
) AS ma_21,
STDDEV(close_price) OVER (
PARTITION BY symbol
ORDER BY date
ROWS BETWEEN 20 PRECEDING AND CURRENT ROW
) AS volatility_21
FROM prices
WHERE date >= '2023-01-01'
""")
df_result.show(10)
3.5.2 Catalyst 优化器
Catalyst 是 Spark SQL 的查询优化器,它通过四个阶段将 SQL 或 DataFrame 操作转换为高效的物理执行计划:
- 分析(Analysis):解析 SQL 语法树,绑定列名与数据类型
- 逻辑优化(Logical Optimization):谓词下推、列剪枝、常量折叠
- 物理计划(Physical Planning):选择 Join 策略、数据源扫描方式
- 代码生成(Code Generation):使用 Tungsten 生成高效 Java 字节码
3.6 Spark Streaming 基础
Spark Streaming 是 Spark 的流处理引擎,支持微批处理(Micro-Batch)和连续处理(Continuous Processing)两种模式。在实时回测场景中,可以使用 Structured Streaming 处理实时行情数据。
3.6.1 Structured Streaming 核心概念
| 概念 | 说明 | 回测场景示例 |
|---|---|---|
| Input Source | 数据源(Kafka、文件、Socket) | 从 Kafka 读取实时行情 |
| Streaming DataFrame | 无界表,持续追加新数据 | 实时 OHLCV 数据流 |
| Trigger | 触发微批处理的时间间隔 | 每 1 秒触发一次回测计算 |
| Output Sink | 结果输出(Console、File、Kafka) | 输出实时信号到交易系统 |
| Watermark | 处理延迟数据的最大等待时间 | 允许行情延迟 5 秒 |
3.6.2 实时回测数据流示例
# 从 Kafka 读取实时行情
df_stream = spark.readStream \
.format("kafka") \
.option("kafka.bootstrap.servers", "localhost:9092") \
.option("subscribe", "market_data") \
.load()
# 解析 JSON 格式的行情数据
from pyspark.sql.functions import from_json, col, to_timestamp
schema = "symbol STRING, price DOUBLE, volume LONG, timestamp STRING"
df_parsed = df_stream.select(
from_json(col("value").cast("string"), schema).alias("data")
).select(
col("data.symbol"),
col("data.price"),
col("data.volume"),
to_timestamp(col("data.timestamp")).alias("event_time")
)
# 添加 Watermark 并计算实时移动平均
df_ma_stream = df_parsed \
.withWatermark("event_time", "10 seconds") \
.groupBy(
col("symbol"),
window(col("event_time"), "1 minute", "30 seconds")
) \
.agg({"price": "avg"}) \
.withColumnRenamed("avg(price)", "ma_price")
# 输出到控制台(调试模式)
query = df_ma_stream.writeStream \
.outputMode("append") \
.format("console") \
.trigger(processingTime="5 seconds") \
.start()
query.awaitTermination()
3.7 本章小结
本章回顾了 Spark 的核心概念:
- RDD 是底层分布式数据集,适合自定义分区和容错逻辑
- DataFrame 是带 Schema 的分布式表,适合 SQL 分析和 ETL
- Dataset 提供类型安全,适合 Scala 环境下的复杂业务逻辑
- SparkSession 是统一入口,管理所有 Spark 功能
- Spark SQL 通过 Catalyst 优化器高效处理结构化数据
- Structured Streaming 支持实时数据流处理,适用于实时回测