DAG 执行框架优于 MapReduce 的地方在哪里?
Created|Updated|系统架构
|Word Count:210|Reading Time:1mins|Post Views:
有个同学问我什么是 DAG 框架。我感觉隐隐约约听过,但又讲不清楚它的概念。
上网搜了一下,我们常见的新大数据执行框架如 Spark、Storm,还有一个我没听过的 Tez,都算 DAG 任务执行框架。他们的主要优点是,可以用 DAG 事先通晓整个任务的全部步骤,然后进行转换优化。如 Tez 就可以把多个任务转换为一个大任务,而 Spark 则可以把相关联的 Map 直接串联起来, 免得多次写回 hdfs(看来 hdfs 也很慢)。传统的 MapReduce 框架为什么不能理解这种优化空间的存在,在任务运行的时候好像一个盲人一样,是个很有意思的话题。
Quora 上的一个相关的问答。
Author: magicliang
Copyright Notice: All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Related Articles
2026-07-13
性能诊断:Spark UI、Event Log 与调优思路
上一篇解决了动态资源分配和调度策略。这一篇进入性能诊断——当 Spark 作业慢了,怎么找到瓶颈。 性能调优的第一步不是改配置,是定位问题。Spark UI 提供了五个关键页面(Jobs、Stages、Storage、Environment、Executors),每个页面指向不同类型的性能问题。Event Log 提供了离线分析的完整数据。 本文只抓一个问题:面对一个慢作业,如何从 Spark UI 的指标出发,沿着诊断路径找到根因。 Spark UI 五个页面 1234567891011121314151617181920212223Jobs 页面 → 每个 Action 对应一个 Job → 关注: Job Duration、Failed Tasks 数量 → 入口: 找到最慢的 Job,点击进入 Stage 详情Stages 页面 → 每个 Stage 的 Task 统计 → 关注: Task Duration 分布、Shuffle Read/Write、GC Time → 核心: 这里是定位性能问题的主战场Storage 页面 → 缓存的 RDD/DataF...
2026-07-26
深入 Hadoop 00 - 导读:节点总会失败
Hadoop 常被介绍为"HDFS + YARN + MapReduce 三驾马车"。这个分类把重点放错了位置。把存储、调度、计算切成三件独立的事情,读者很难回答下面这个问题:为什么这三个子系统会一起出现在同一个项目里,而不是像 Spark、Flink、Kafka 那样作为独立组件存在? 更准确的说法是:Hadoop 是一组关于"如何构建运行在大量廉价节点上的分布式系统"的前提假设在三个不同层面的具体化。这组前提把整个项目串成一条主线——节点总会失败、数据总是巨大、廉价比专用更重要。HDFS、YARN、MapReduce 各自处理这组前提下的一个子问题:HDFS 把大文件切成块并多副本放置,YARN 把集群资源切成容器并按策略分配,MapReduce 把大计算切成任务并按失败重试执行。 本系列只抓一个问题:Hadoop 这套以"节点总会失败"为前提假设的工程化方案,在 HDFS、YARN、MapReduce 三个层面是如何具体实现的,每个实现里有哪些可以迁移到其他分布式系统的设计模式。 GFS 论文留下的设计遗产 要理解...
2026-07-13
宽依赖与窄依赖:Stage 是怎样划分出来的
上一篇拆解了 RDD 的五大属性,其中 dependencies 决定了 RDD 之间的依赖类型。这一篇进入依赖类型的核心区分——宽依赖和窄依赖——以及它如何直接决定 Stage 的划分。 宽依赖和窄依赖容易被理解成"一对一"和"多对多"的分区关系。更准确的说法是:窄依赖意味着子 RDD 的每个分区只依赖父 RDD 的固定少数分区,可以在单个 Task 内完成计算;宽依赖意味着子 RDD 的分区需要读取父 RDD 所有分区的数据,必须等待一次全量数据交换(Shuffle)。 本文只抓一个问题:DAGScheduler 按什么规则把 RDD 依赖图切分成 Stage。 下图展示了 Stage 划分的核心规则——窄依赖 pipeline 执行,宽依赖切出新 Stage: 窄依赖的三种形态 窄依赖(NarrowDependency)的定义是:父 RDD 的每个分区最多被子 RDD 的一个分区使用。在 Spark 源码中,NarrowDependency 有两个具体子类: OneToOneDependency:父子分区一一对应。map、filte...
2026-07-13
内存管理与 Tungsten:堆外内存、序列化与代码生成
上一篇解决了 Shuffle 的物理机制。这一篇进入内存管理——Spark 如何突破 JVM 的内存瓶颈。 Spark 是一个 JVM 应用,天然受制于 Java 的内存模型:对象头开销大、GC 停顿不可控、序列化效率低。Project Tungsten 是 Spark 为解决这三个问题发起的底层优化计划,它从三条线同时推进——堆外内存管理、二进制数据格式、全阶段代码生成。 本文只抓一个问题:Tungsten 的三条优化线分别解决了什么问题,以及 Spark 的统一内存管理模型如何在执行内存和存储内存之间做动态调配。 下图展示了 Executor 的统一内存管理模型——Execution Memory 和 Storage Memory 之间的动态借用机制: Java 对象的内存开销 一个 Java 字符串 “abcd” 在堆内占多少字节? 12345678910111213java.lang.String 对象: 对象头: 12 bytes (64-bit JVM, 压缩指针) hash: 4 bytes (int) value[]: 4 bytes ...
2026-07-13
DataFrame 与 Dataset:类型安全与性能的折中
上一篇解决了 Catalyst 优化器的四阶段流水线。这一篇进入用户 API 层——RDD、DataFrame、Dataset 三代 API 的演进逻辑。 三者的区别容易被简化为"RDD 是低级 API,DataFrame 是高级 API"。更准确的说法是:三代 API 在类型安全和执行优化之间做了不同的取舍。RDD 完全类型安全但无法被 Catalyst 优化;DataFrame 完全被 Catalyst 优化但放弃了编译期类型检查;Dataset 试图兼顾两者,但付出了 Encoder 序列化/反序列化的代价。 本文只抓一个问题:三代 API 在内部表示、优化路径和序列化机制上的具体差异。 三代 API 的内部表示 1234567891011121314RDD[Person] └─ 内部存储: JVM 堆上的 Java/Scala 对象 └─ 优化路径: 无(用户代码是黑盒,Spark 无法查看函数内部) └─ 类型信息: 编译期完整保留(泛型参数 T = Person)DataFrame (= Dataset[Row]) └─ 内部存储: Uns...
2025-07-29
经典面试问题的大数据解法——Spark 与 Flink 实战
“100 亿个数中找出最大的 1000 个”、“两个 10GB 的文件找出共同的 URL”——这些经典面试题的本质都是内存放不下。单机方案围绕分治展开,分布式方案则把分治思想映射到集群节点上。本文按问题类型组织,每类问题给出从单机到 Spark/Flink 的渐进式解法,并附上概率数据结构(布隆过滤器、HyperLogLog、Count-Min Sketch)在近似场景中的应用。 引言:大数据问题的共同特征 为什么"内存放不下" 面试中给出的数据规模往往是精心设计的——刚好跨过单机内存的边界: 数据规模 内存需求 典型服务器内存 能否放入内存 1 亿个 int 400 MB 16 GB ✅ 10 亿个 int 4 GB 16 GB ✅(但留给程序的余量不多) 100 亿个 int 40 GB 16 GB ❌ 10 亿个 URL(平均 100 字节) 100 GB 16 GB ❌ 上表只计算了裸数据大小。实际使用 HashMap、HashSet 等容器时,对象头、指针、负载因子会使内存占用膨胀 3-5 倍。 通用解题框架 123...
Announcement
人生只是,守株待兔
