内存管理与 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 ...
Shuffle 机制:数据跨分区交换的代价与优化
上一篇解决了 DAGScheduler 和 TaskScheduler 的分工。这一篇进入 Shuffle 机制——Stage 之间数据交换的物理过程。 Shuffle 容易被理解成"把数据从一组节点传到另一组节点"。更准确的说法是:Shuffle 是一个分布式的排序-分区-传输流水线,它把上游 Stage 每个分区的输出按 key 的目标分区号排序写入磁盘,然后由下游 Stage 的 Task 跨网络拉取属于自己的那部分数据。 本文只抓一个问题:SortShuffleManager 的 Shuffle Write 和 Shuffle Read 两个阶段各做了什么,以及为什么 Shuffle 是 Spark 作业的头号性能瓶颈。 下图展示了 Shuffle 的完整数据流——从 Map 端排序写盘到 Reduce 端拉取归并: Shuffle 的全局视角 一个 Shuffle 操作(如 reduceByKey)在物理层面涉及两组 Task: 12345678910111213上游 Stage (M 个 ShuffleMapTask) 下游 S...
DAG Scheduler 与 Task Scheduler:从逻辑计划到物理执行
上一篇解决了 Stage 的划分规则——遇到 ShuffleDependency 就切一刀。这一篇进入调度器内部。 Stage 划分出来之后,谁来决定 Stage 的提交顺序?谁来把 Stage 拆成 Task 发给 Executor?Spark 用两层调度器分工完成这件事:DAGScheduler 负责 Stage 级别的依赖分析和提交顺序,TaskScheduler 负责 Task 级别的资源分配和执行调度。 本文只抓一个问题:一个 action 触发之后,从 Job 到 Stage 到 Task 再到 Executor,调度链路上每一步发生了什么。 调度全景 12345678910111213141516171819202122232425用户代码: rdd.count() │ ▼ SparkContext.runJob() │ ▼ DAGScheduler.submitJob() │ 构建 Stage DAG,按依赖顺序提交 ▼ DAGScheduler.submitStage() │ 检查父 S...
宽依赖与窄依赖: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...
RDD:弹性分布式数据集的五大属性
上一篇确立了 Spark 的核心是一张 DAG,而 DAG 的节点就是 RDD。这一篇进入 RDD 本身。 RDD 容易被理解成"分布式的数组"或者"分布在多台机器上的数据集合"。更准确的说法是:RDD 是一份计算配方,记录了数据从哪来、经过什么变换、丢失一个分区后怎么重算。 本文只抓一个问题:RDD 的五大属性分别控制了什么,以及这五个属性如何支撑 Spark 的调度和容错。 五大属性总览 Spark 源码中 RDD 的抽象类定义了五个方法,每个方法对应一个核心属性: 123456RDD[T]├── getPartitions: Array[Partition] ← 数据怎么切分├── getDependencies: Seq[Dependency[_]] ← 上游是谁├── compute(split, context): Iterator[T] ← 一个分区怎么算├── partitioner: Option[Partitioner] ← 按什么规则分区└── getPreferredLocations(s...
导读:为什么 Spark 的核心是一张 DAG
Spark 常被介绍为"比 MapReduce 快 100 倍的计算引擎"。这个说法把重点放错了地方。速度差异只是结果,不是原因。真正的区别在于执行模型:MapReduce 是一条固定的 Map-Shuffle-Reduce 三段流水线,而 Spark 是一个可以表达任意有向无环图(DAG)的执行引擎。 本篇只抓一个问题:Spark 的 DAG 执行模型到底和 MapReduce 的线性管道有什么本质区别,以及这个区别如何贯穿整个系列。 MapReduce 的线性管道 MapReduce 的执行模型可以用一句话概括:读数据 → Map → 写磁盘 → Shuffle → 写磁盘 → Reduce → 写数据。 1HDFS → Map Task → Local Disk → Shuffle → Local Disk → Reduce Task → HDFS 这条管道有两个结构性约束: 第一,每个 MapReduce 作业只有一轮 Map 和一轮 Reduce。如果业务逻辑需要多轮聚合(比如先按用户分组,再按地区汇总),就必须把逻辑拆成多个 MapReduce 作...
从规则到统计:人工智能七十年的两条路线之争
人工智能的历史,本质上是一部关于"什么是智能"的争论史。 从 1950 年代至今,这个领域经历了至少两次大规模繁荣和两次寒冬,催生了符号主义、连接主义、统计学习、深度学习、强化学习等多个范式。这些范式的兴衰,表面上看是技术路线的竞争,底层驱动力却是对一个哲学问题的不同回答:智能究竟是"规则的执行"还是"数据的涌现"? 一、两种智能观的源头 1.1 符号主义:智能就是符号操作 1943 年,Warren McCulloch 和 Walter Pitts 发表了一篇论文,用数理逻辑描述神经元的行为。这篇论文同时启发了两个方向——一个方向关注"逻辑"本身,另一个方向关注"神经元网络"本身。前者演化为符号主义(Symbolism),后者演化为连接主义(Connectionism)。 符号主义的哲学根基可以追溯到莱布尼茨。莱布尼茨试图创建一种"通用逻辑演算"来表达所有人类思想,17 世纪的霍布斯则主张推理"不过是计算"。这条线索在 20 世纪中叶被 ...
美日开战之前,美国偏袒过日本吗——核查"日本子弹七成靠美国"的说法
九一八之后,尤其是七七事变之后,中文世界一直流传一种说法:美国一边在道义上谴责日本侵略,一边继续跟日本做生意,卖给日本的石油、废钢比卖给中国的还多,所以日本用来打中国的子弹,“七成以上其实是美国人间接造的”。这个判断在情绪上很有冲击力,在朋友圈和短视频里反复出现,但它有两个一直没被认真处理的问题:第一,那个"七成"到底是怎么算出来的,是不是一个能站住的统计;第二,"美国偏袒日本"这个定性,能不能被美方自己的贸易数据和政策档案支持。 把这两个问题拆开看,结论并不是简单的"是"或"否"。事实内核大体成立——直到 1940 年,美国确实是给日本战争机器输血最多的国家,而且这种输血在南京大屠杀之后又持续了整整三年。但"偏袒"这个定性,以及"七成子弹"这个具体数字,都需要修正。美国不是在"援助"日本,而是在一套孤立主义的自由贸易法律框架下"卖货"给日本;与此同时,它是最早拒绝承认日本征服成果的大国,一直在给中国放贷款,并且从 193...
AI 是放大器不是方向:读 Adam Bender 谈软件生态学的 10 倍时刻
引子:10 倍代码不等于 10 倍工程 Google 首席软件工程师 Adam Bender 在 Google I/O 2026 的一场演讲里抛出大多数人还没来得及思考的问题:当 AI 把代码产出速度推到现有工程流程无法承载的地步,开发者生态系统里什么会最先垮掉? 他的回答直白:今天构建软件的方式,在 10 倍速度下根本行不通。AI 时代的真正赢家不是产出最高的团队,而是基本功最扎实的那些人。AI 只负责放大,不负责方向。 这场演讲串起一个大多数人没听过的概念:软件生态学,对生产软件的社会技术生态系统进行整体性研究的学科。它要求不只看技术,还要看人、看文化、看组织里那些不成文的规则。 软件生态学:被忽视的整体视角 系统、生态系统与社会技术系统 要理解软件生态学,得先回到三个基础概念。 系统是一组相互关联的元素,按照一套规则运作,形成统一的整体。空调就是系统:恒温器知道目标温度,暖通空调负责升温或降温,房间温度合适了信号就停。 生态系统是特殊的系统,由相互依赖的行动者组成的动态网络,这些行动者与其环境共同演化,具有涌现行为和去中心化的自主性。关键点是,环境是系统的一部分,不能把两...
CI/CD 方法论的前世今生
软件工程有一条反直觉的规律:集成的频率越高,每次集成的痛苦越小。这条规律从 1990 年代末被 Kent Beck 写进极限编程的实践清单,到今天演化出一整套涵盖构建、测试、部署、发布的自动化体系。CI/CD 不是某一个工具或某一种流程,而是三十年工程实践反复迭代的产物。 持续集成的诞生(1996-2006) 极限编程与 CI 的起源 1996 年,Kent Beck 加入克莱斯勒公司的 C3(Chrysler Comprehensive Compensation System)项目。这个项目此前陷入僵局,Beck 将它作为极限编程(XP)的试验田,引入了十二条核心实践,持续集成是其中之一。CI 的核心主张很简单:开发者每天多次将代码集成到主干,每次集成都触发自动构建和测试。1999 年出版的《Extreme Programming Explained: Embrace Change》正式将 CI 确立为一个命名实践。 CI 要解决的问题在当时有一个形象的说法叫"集成地狱"(integration hell)——团队各自开发数周甚至数月,最后合并代码时发现冲突...







