Spark
把中间结果放内存,比 MapReduce 快一个数量级
Spark 把“读数据 → 变换 → 输出”描述成一张 DAG,先不执行,等遇到 action 才整体优化、切成 Stage 并行跑。中间结果留在内存而不是写回磁盘,所以迭代计算(机器学习、图计算)比 MapReduce 快 10 倍以上。
它解决什么问题
MapReduce 每一步都要把结果写 HDFS 再读回来,一个十步的作业要读写十次磁盘;而且只会批处理,实时场景没法用。
真实场景:典型场景:每天凌晨用 Spark SQL 跑几千个离线指标,读 Hive 表、join 十几张维表、写回数仓,两小时内跑完。
核心概念
每个概念一句话 + 一个类比。点击卡片看更详细的解释。
RDD
不可变、可分区的数据集合,记的是“怎么算出来的”而不是数据本身
类比:菜谱,每一步都记着,锅糊了可以照着重做
看概念详情DAG
把一串转换操作画成图,Spark 据此优化执行顺序
类比:施工流程图,先地基再砌墙,顺序不能乱
看概念详情Stage
遇到 shuffle 就被切开,一个 Stage 内部可以流水线执行
类比:接力赛的分段,交棒(shuffle)处必须等齐人
看概念详情Shuffle
把数据按 key 重新分发到不同节点,是最贵的一步
类比:把一摞扑克按花色重新分给四个人
看概念详情宽窄依赖
窄依赖一个分区只依赖一个父分区,宽依赖要等所有父分区
类比:一对一交接班是窄,全员到齐才能开会是宽
看概念详情数据流转动画
跟着步骤看一条数据从进入系统到被处理完的完整路径,可以暂停、重播和调速。
一个 Spark 作业从提交到出结果的路径
← 左右滑动查看完整流程图 →
代码里的 map / filter / join 都是懒执行,只是往 DAG 上加节点,直到遇到 count、write 这类 action。
提示:注意 shuffle:它是 Stage 的分界线,也是绝大多数性能问题的来源。 悬停图中的节点可以看概念解释。
优劣势
短句说清适合什么、不适合什么,选型时先看这一栏。
- 内存计算 + DAG 优化,迭代任务比 MapReduce 快一个数量级
- 一套引擎覆盖 SQL、流(Structured Streaming)、MLlib、GraphX
- API 友好,Python / SQL 都能写,门槛比 MapReduce 低太多
- 生态最好,和 Hive、HDFS、Kafka、Delta / Iceberg 全打通
- 不是纯流处理,Structured Streaming 的延迟在秒级,做不到毫秒
- shuffle 依然要落盘,数据倾斜会让个别 Task 拖垮整个作业
- 内存吃得多,参数没调好容易 OOM
- 小作业启动开销大(几十秒),不适合交互式即席查询
常见使用场景
每个场景都配一句「为什么用它」。
离线数仓 ETL
每天把 ODS 层原始数据清洗、join、聚合,写到 DWD / DWS 层。
为什么用它:Spark SQL 兼容 Hive 语法,迁移成本低,跑批稳定。
机器学习特征工程
用 MLlib 做特征抽取、模型训练,中间结果反复迭代。
为什么用它:迭代计算正是内存计算的强项,比 MapReduce 快十倍。
日志清洗与入湖
读 Kafka / HDFS 的原始日志,解析、去重、打成 Parquet 入湖。
为什么用它:批流一套代码,白天微批、晚上全量,维护成本低。
同类对比
和同赛道的组件并排看,差异一目了然。
一句话:离线与准实时选 Spark,真实时选 Flink。两者不是替代关系,很多公司是 Spark 跑批、Flink 跑流,共用一套 Kafka 和数仓。
常见问题
上手第一个月最容易卡住的地方。
Q1Spark 一定比 MapReduce 快吗?
单次简单作业不一定,Spark 启动本身要几十秒;但迭代型(机器学习、图计算)和多次 shuffle 的作业,Spark 快 5~10 倍是常态,因为中间结果不落 HDFS。数据量很小(几 GB)时,用 Spark 反而更慢。
Q2数据倾斜怎么处理?
先看 Spark UI 里 Task 的耗时分布,某个 Task 跑几小时、其他几分钟就是倾斜。常用手段:加盐打散热点 key、把大表 broadcast 掉小表、两阶段聚合(先局部聚合再加随机前缀)、开启 AQE 的 skew join 处理。
Q3Spark 和 Flink 怎么选?
离线批处理、机器学习、已有 Hive 体系,选 Spark;要求毫秒到秒级实时、事件驱动、状态复杂(CEP、风控),选 Flink。现在两家都在做流批一体,但基因不同:Spark 是批为主,Flink 是流为主。
Q4为什么我的作业总是 OOM?
常见三个原因:collect 把大结果拉回 Driver、broadcast 了大表、shuffle 分区数太少导致单分区数据过大。先调 spark.sql.shuffle.partitions(默认 200 往往偏小),再检查是否用了 collect。