01学完你能做到
- 说清 Driver、Executor、分区、惰性执行、Shuffle 和 Stage 的关系。
- 看懂执行计划中的
Exchange、列裁剪和分区裁剪。 - 判断什么时候该用 Spark,什么时候单机工具更合适。
02这课解决什么问题
盘后要重算全市场历史 K 线,或者给过去多年的数据增加一个新因子时,怎样把大任务拆开并行执行,而且某台机器失败后不用全部从头开始?
03核心概念
Driver、Executor 与集群管理器
Driver 是任务的协调者:读取程序、生成执行计划、把任务分发出去并汇总结果。Executor 在工作节点上真正读取数据和计算。Cluster Manager(例如 YARN、Kubernetes 或 Standalone)负责分配资源。
分区与惰性执行
Spark 会把数据拆成多个 partition,每个任务处理其中一部分。存储分区决定需要读取哪些文件,计算分区决定同时运行多少个任务,两者有关但不是同一个概念。
filter、select、groupBy 等 transformation 先记录计算计划;count、show、write 等 action 才触发执行。这样 Spark 可以统一优化,例如只读需要的列和日期分区。
Shuffle、依赖与 Stage
按股票代码 groupBy("code") 时,同一代码的数据要被重新分到同一个任务,这种跨分区重分发叫 Shuffle,通常涉及网络和磁盘,是批处理的高成本步骤。过滤、增加列等不需要跨分区交换的操作属于窄依赖;需要 Shuffle 的属于宽依赖。Spark 通常以 Shuffle 为边界切分 Stage。
Spark 还会记录数据从输入到结果经历的计算关系(lineage)。某个 Executor 失败时,系统可以重算丢失的分区,而不必重跑整个作业。自适应查询执行(AQE)则会根据运行中的数据量调整计划,例如合并过小的分区、处理数据倾斜。
04在行情数据链路中的位置
原始 Tick / 快照
↓
Spark 批作业:聚合、复权、历史回补
↓
质量校验
↓
日 K、分钟 K、因子表
↓
API 或其他数据服务盘中实时结果可以先提供低延迟版本,盘后再用完整数据批量重算,并用最终结果覆盖临时结果。批作业应支持按日期范围运行,方便修复单日或一段历史。
05一个最小例子
from pyspark.sql import SparkSession, functions as F
spark = (SparkSession.builder
.master("local[4]")
.appName("daily-bars")
.config("spark.sql.shuffle.partitions", "8")
.getOrCreate())
ticks = spark.read.parquet("data/ticks")
bars = (ticks.groupBy("code", "trade_date")
.agg(F.min_by("price", "ts").alias("open"),
F.max("price").alias("high"),
F.min("price").alias("low"),
F.max_by("price", "ts").alias("close"),
F.sum("volume").alias("volume")))
bars.explain()
bars.write.mode("overwrite").partitionBy("trade_date").parquet("data/bars")min_by("price", "ts") 按时间取最早成交价。分布式数据没有天然的行顺序,不能把 first("price") 当作开盘价。
如果数据按 trade_date 分区存储,下面的过滤可以触发分区裁剪:
ticks.filter("trade_date = '2026-10-28'").groupBy("code").count().explain()执行计划中出现 PartitionFilters,说明其他日期的目录可以跳过;出现 Exchange,说明发生了 Shuffle。
06常见误区
- 数据只有几 GB 也强行使用 Spark。pandas、Polars 或 DuckDB 在单机可处理时通常更简单、更快。
- 以为写完 transformation 就已经计算。没有 action,计算通常还没有真正发生。
- 忽略 Shuffle 分区数。数据很小时会产生大量空任务,数据很大时又可能单个任务过重。
- 只按股票分组却不处理数据倾斜。个别活跃股票可能让少数任务远慢于其他任务。
- 按日期和代码制造大量小文件。写出前要控制分区数,避免下游读取被小文件拖慢。
- 重跑时直接追加写,导致结果重复。输出应按分区覆盖,或采用其他幂等写入方式。
07延伸阅读
08一分钟讲给别人
试着用自己的话讲一遍Spark 像一个批量加工的调度团队:Driver 拆任务,Executor 并行干活,分区决定任务颗粒度。它不会看到一行代码就马上计算,而是等真正需要结果时统一优化。按键分组时数据要重新分发,这一步叫 Shuffle,通常最费资源。数据不大时用单机工具更划算,数据很大或要反复回补时,Spark 的并行和容错才有价值。
5 道自测
先在心里答一遍,再展开对照。能说清理由,比答对数字更重要。
01Driver 和 Executor 分别负责什么?
02什么是惰性执行?
03执行计划中的 `Exchange` 通常说明什么?
04为什么不能用 `first("price")` 直接当开盘价?
05存储分区和计算分区有什么区别?
已看 0/5 题
练动手练习
任务:对一个按 trade_date 分区的 Parquet Tick 表,分别执行按日期过滤和按代码过滤,并比较 explain() 中的 PartitionFilters 与 DataFilters。再检查生成日 K 的计划,找出 Shuffle 的位置。
完成这一课
读完讲义、看完自测答案,这一课就算学完了。
- 读完讲义
- 看完全部自测
本课为投资者教育内容,案例数字均为示意,不构成任何投资建议,也不承诺收益。规则和费率会变,实际操作前请以交易所、监管机构和产品的最新公告为准。