第7章:分布式系统架构
"我们不是在构建服务器,而是在构建一台行星级的计算机。" - Luiz André Barroso
概述
Google的分布式系统架构代表了21世纪计算机科学最重要的创新之一。从最初的几台服务器到如今横跨全球的数百万台机器,Google不仅解决了前所未有的规模挑战,更重要的是,他们将这些解决方案抽象化、论文化,深刻影响了整个工业界。
本章将深入探讨Google分布式系统的核心组件、设计理念和架构演进,从工程师视角理解这些系统如何协同工作,支撑起每天处理数十亿请求的服务。
7.1 架构演进历程
7.1.1 初创时期 (1998-2002):从车库到机房
硬件哲学的诞生
1998年,Larry Page和Sergey Brin在斯坦福大学的宿舍里组装了第一批服务器。这些服务器使用廉价的商用硬件(COTS - Commercial Off-The-Shelf),奠定了Google独特的硬件哲学基础。
早期架构 (1998-1999)
┌──────────────────────────────────────┐
│ 用户浏览器 │
└────────────────┬─────────────────────┘
│HTTP
┌───────▼────────┐
│ Web Server │
│ (Apache/自研) │
└───────┬────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Index │ │Crawler │ │PageRank│
│Server │ │Server │ │Server │
└────────┘ └────────┘ └────────┘
│ │ │
└────────────┼────────────┘
▼
┌──────────────┐
│ File System │
│ (单机) │
└──────────────┘
关键决策:
- 使用廉价PC而非昂贵的服务器
- 软件层面处理硬件故障
- 横向扩展而非纵向扩展
7.1.2 基础设施奠基 (2003-2006):三驾马车
这个时期,Google发表了三篇影响深远的论文,被称为分布式系统的"三驾马车":
-
Google File System (GFS) - 2003 - 解决问题:海量数据的可靠存储 - 核心贡献者:Sanjay Ghemawat, Howard Gobioff, Shun-Tak Leung
-
MapReduce - 2004 - 解决问题:大规模数据并行处理 - 核心贡献者:Jeffrey Dean, Sanjay Ghemawat
-
Bigtable - 2006 - 解决问题:结构化数据的分布式存储 - 核心贡献者:Fay Chang, Jeffrey Dean, Sanjay Ghemawat等
三驾马车架构关系
┌─────────────────────┐
│ 应用层 │
│ (Search, Gmail等) │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ MapReduce │
│ (计算框架) │
└──────────┬──────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Bigtable │ │直接访问 │ │其他存储 │
│结构化 │ │ GFS │ │ 系统 │
└────┬────┘ └─────────┘ └─────────┘
│ │ │
└──────────────┼──────────────┘
▼
┌───────────────────┐
│ GFS │
│ (分布式文件系统) │
└───────────────────┘
7.1.3 规模化时代 (2007-2012):全球化架构
Borg系统的诞生
2007年左右,Google内部开始使用Borg系统进行大规模集群管理。虽然Borg论文直到2015年才发表,但这个系统从2007年就开始支撑Google的所有服务。
Borg时代的数据中心架构
┌────────────────────────────────────────┐
│ 全球负载均衡器 │
│ (Maglev/GSLB) │
└─────────┬──────────────────────────────┘
│
┌─────┼─────┬──────────┬──────────┐
▼ ▼ ▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ 美国数据中心 │ │ 欧洲数据中心 │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ Borg Master │ │ │ │ Borg Master │ │
│ └──────┬──────┘ │ │ └──────┬──────┘ │
│ │ │ │ │ │
│ ┌──────▼──────┐ │ │ ┌──────▼──────┐ │
│ │ Borglets │ │ │ │ Borglets │ │
│ │ (1000s节点) │ │ │ │ (1000s节点) │ │
│ └─────────────┘ │ │ └─────────────┘ │
└───────────────────┘ └───────────────────┘
Spanner的突破
2012年,Google发表Spanner论文,首次实现了全球分布式的强一致性数据库。
关键创新:
- TrueTime API:全球时钟同步
- 多版本并发控制(MVCC)
- 自动数据分片和迁移
7.1.4 云原生时代 (2013-2020):开源与标准化
Kubernetes的诞生
2014年,Google开源Kubernetes,将Borg的经验带给整个行业。
Kubernetes vs Borg 对比
┌─────────────────────────────────────┐
│ Borg (内部) │
├─────────────────────────────────────┤
│ - 专有系统,深度定制 │
│ - 单体架构 │
│ - 与Google基础设施紧密集成 │
│ - 支持数百万容器 │
└─────────────────────────────────────┘
↓ 经验提炼
┌─────────────────────────────────────┐
│ Kubernetes (开源) │
├─────────────────────────────────────┤
│ - 通用容器编排 │
│ - 微服务架构 │
│ - 云厂商中立 │
│ - 可扩展API │
└─────────────────────────────────────┘
7.1.5 AI基础设施时代 (2020-至今)
随着AI工作负载的爆发式增长,Google的分布式系统架构进入新阶段。
新一代架构特征:
- TPU Pod:专用AI计算集群
- 流式处理:Dataflow
- 无服务器:Cloud Run
- 边缘计算:分布式推理
现代Google架构栈 (2024)
┌──────────────────────────────────┐
│ 应用层 │
│ (Search, YouTube, Gmail等) │
└────────────┬─────────────────────┘
│
┌────────────▼─────────────────────┐
│ 服务网格 │
│ (Istio/Envoy based) │
└────────────┬─────────────────────┘
│
┌────────────▼─────────────────────┐
│ 容器编排层 │
│ (Borg + Kubernetes) │
└────────────┬─────────────────────┘
│
┌────────────▼─────────────────────┐
│ 统一存储层 │
│ (Colossus + Spanner + Bigtable)│
└────────────┬─────────────────────┘
│
┌────────────▼─────────────────────┐
│ 硬件抽象层 │
│ (CPU + GPU + TPU + 网络) │
└──────────────────────────────────┘
7.2 GFS:分布式文件系统的开创者
7.2.1 设计背景与挑战
2003年,Google面临的存储挑战:
- 数TB级别的数据存储需求
- 硬件故障是常态而非异常
- 文件大小从几KB到几GB不等
- 大部分是追加写入而非随机写入
设计假设:
- 系统由廉价的商用组件构成,故障频繁
- 存储大文件(通常100MB以上)
- 工作负载主要是大规模流式读取和追加写入
- 应用和文件系统API可以协同设计
7.2.2 架构设计
GFS架构图
┌──────────────┐
│ GFS Client │
└──────┬───────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ 控制流 ┌──────────────┐
│ GFS Master │◄────────┤应用程序 │
│ │ │ │
│ ·命名空间 │ └──────┬───────┘
│ ·元数据 │ │数据流
│ ·chunk位置 │ │
└──────┬───────┘ │
│ │
│控制消息 │
│ │
┌───┼────────────────────────┼───┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Chunkserver│ │Chunkserver│ │Chunkserver│
│ │ │ │ │ │
│ ·64MB块 │ │ ·64MB块 │ │ ·64MB块 │
│ ·3副本 │ │ ·3副本 │ │ ·3副本 │
└───────────┘ └───────────┘ └───────────┘
Linux文件系统 Linux文件系统 Linux文件系统
核心组件:
-
Master节点(单点) - 维护所有文件系统元数据 - 管理chunk租约和垃圾回收 - 处理chunk迁移和负载均衡
-
Chunkserver(数千个) - 存储64MB的chunk - 每个chunk默认3个副本 - 直接与客户端交互进行数据传输
-
客户端库 - 实现文件系统API - 缓存元数据 - 直接与chunkserver通信
7.2.3 关键机制
- 租约机制(Lease)
写入流程
1. Client → Master: 请求写入文件
2. Master → Client: 返回主副本和其他副本位置
3. Client → 所有副本: 推送数据(流水线)
4. Client → 主副本: 发送写入请求
5. 主副本 → 其他副本: 转发写入请求
6. 其他副本 → 主副本: 确认完成
7. 主副本 → Client: 返回结果
-
一致性模型 - 定义一致(defined):所有客户端看到相同数据 - 一致(consistent):所有客户端看到相同数据,且是某次写入的完整结果 - GFS保证:追加操作是原子的且至少一次
-
容错机制
故障处理策略
┌────────────────────────────────┐
│ 故障类型 │
├────────────────────────────────┤
│ Chunkserver故障: │
│ - 心跳检测(60秒超时) │
│ - 自动重新复制到3副本 │
│ │
│ Master故障: │
│ - 操作日志复制 │
│ - Shadow Master热备 │
│ │
│ 数据损坏: │
│ - 32位校验和 │
│ - 后台扫描验证 │
└────────────────────────────────┘
7.2.4 性能优化
-
大块设计(64MB) - 减少元数据开销 - 减少客户端与master交互 - 提高网络利用率
-
流水线复制
数据流优化
Client ──────► Replica A ──────► Replica B ──────► Replica C
最近 次近 最远
(网络拓扑距离)
- 快照机制 - Copy-on-write实现 - 几乎瞬时完成 - 用于备份和实验
7.2.5 从GFS到Colossus
2010年后,Google开发了下一代文件系统Colossus:
| 特性 | GFS | Colossus |
| 特性 | GFS | Colossus |
|---|---|---|
| Master | 单点 | 分布式(多个元数据服务器) |
| 块大小 | 64MB固定 | 1MB可变 |
| 客户端缓存 | 有限 | 更智能的缓存 |
| 规模 | PB级 | EB级 |
| 延迟 | 毫秒级 | 微秒级 |
Colossus架构演进
GFS (2003) Colossus (2010+)
┌──────────────┐ ┌─────────────────────┐
│ Single Master│ │ Distributed Masters │
└──────┬───────┘ └──────────┬──────────┘
│ │
│ ┌───────────┼───────────┐
│ ▼ ▼ ▼
┌──────▼───────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Chunkservers │ │ Curator │ │ Mixer │ │ D-Server │
│ (1000s) │ │ (元数据) │ │ (负载) │ │ (数据) │
└──────────────┘ └──────────┘ └──────────┘ └──────────┘
7.3 MapReduce:大数据处理的编程范式
7.3.1 诞生背景
2004年,Jeffrey Dean和Sanjay Ghemawat面临的问题:
- Google需要处理数百TB的数据
- 程序员花费大量时间处理分布式计算的复杂性
- 需要一个简单的编程模型隐藏分布式细节
核心洞察: 大多数数据处理可以抽象为两个简单操作:Map和Reduce
7.3.2 编程模型
MapReduce计算模型
输入数据 ──────► Map阶段 ──────► Shuffle阶段 ──────► Reduce阶段 ──────► 输出结果
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
(K1,V1) → map(K1,V1) → list(K2,V2) → (K2,list(V2)) → reduce(K2,list(V2)) → list(K3,V3)
示例:词频统计
"hello world" → [("hello",1),("world",1)] → ("hello",[1,1]) → ("hello",2)
"hello google" → [("hello",1),("google",1)] → ("world",[1]) → ("world",1)
→ ("google",[1]) → ("google",1)
编程接口:
// 用户只需实现这两个函数
class Mapper {
virtual void Map(const string& key, const string& value,
OutputCollector* output) = 0;
};
class Reducer {
virtual void Reduce(const string& key,
const Iterator<string>& values,
OutputCollector* output) = 0;
};
7.3.3 执行流程
MapReduce执行架构
┌─────────────┐
│ Master │
│ (调度器) │
└──────┬──────┘
│
┌──────────────────┼──────────────────┐
│ │ │
分配Map任务 分配Reduce任务 监控和容错
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────┐
│ Worker节点池 │
├───────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Worker 1 │ │Worker 2 │ │Worker 3 │ ... │
│ │执行Map │ │执行Map │ │执行Reduce│ │
│ └─────────┘ └─────────┘ └─────────┘ │
└───────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ GFS │ │ GFS │ │ GFS │
│输入文件│ │中间文件│ │输出文件│
└────────┘ └────────┘ └────────┘
详细执行步骤:
-
输入分片 - Master将输入文件分成16-64MB的片 - 创建M个Map任务和R个Reduce任务
-
Map阶段 - Worker读取输入分片 - 解析出key/value对 - 调用用户Map函数 - 缓冲中间结果到内存
-
Shuffle阶段 - Map输出按key哈希到R个分区 - 周期性写入本地磁盘 - 位置信息报告给Master
-
Reduce阶段 - Reducer从Map worker拉取数据 - 按key排序合并 - 调用用户Reduce函数 - 输出写入GFS
7.3.4 容错机制
容错策略
┌─────────────────────────────────────────┐
│ Worker故障 │
├─────────────────────────────────────────┤
│ Map Worker故障: │
│ - Master检测(心跳超时) │
│ - 重新调度所有Map任务 │
│ - 通知Reducer新位置 │
│ │
│ Reduce Worker故障: │
│ - 重新调度未完成的Reduce任务 │
│ - 从Map Worker重新读取数据 │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Master故障 │
├─────────────────────────────────────────┤
│ - 周期性写入检查点 │
│ - 故障后从检查点恢复 │
│ - 或者直接重新执行整个作业 │
└─────────────────────────────────────────┘
7.3.5 优化技术
- 数据本地性
调度优化
优先级1: 本地数据 ──────► Worker和数据在同一机器
优先级2: 同机架 ──────► Worker和数据在同一机架
优先级3: 跨机架 ──────► Worker和数据在不同机架
-
备份任务(Backup Tasks) - 检测"掉队者"(stragglers) - 在作业快结束时启动备份执行 - 显著减少总完成时间(可达44%)
-
Combiner函数
Combiner优化(局部聚合)
Map输出: [("the",1),("the",1),("the",1)]
↓ Combiner
本地聚合: [("the",3)]
↓
网络传输量减少
7.3.6 性能数据
Google 2004年的基准测试:
| 测试类型 | 数据量 | 机器数 | 总时间 | 吞吐率 |
| 测试类型 | 数据量 | 机器数 | 总时间 | 吞吐率 |
|---|---|---|---|---|
| 排序 | 1TB | 1800 | 891秒 | 1.12GB/s |
| 搜索 | 1TB | 1800 | 150秒 | 6.67GB/s |
真实应用案例:
- 网页索引:处理20TB数据
- 日志分析:每天处理数PB日志
- 机器学习:训练大规模模型
7.3.7 MapReduce的影响
开源实现:
- Apache Hadoop(2006):Yahoo主导
- Apache Spark(2014):内存计算
- Apache Flink(2015):流处理
MapReduce生态系统演进
MapReduce (2004)
│
┌──────────┼──────────┐
▼ ▼ ▼
Hadoop Spark Flink
(2006) (2014) (2015)
│ │ │
批处理 内存计算 流处理
编程范式影响:
- 函数式编程在大数据处理中的应用
- 声明式编程模型的流行
- 分布式计算框架的标准化
7.4 Bigtable:NoSQL数据库的先驱
7.4.1 设计动机
2006年,Google面临的结构化数据存储挑战:
- 需要存储数百TB到PB级别的结构化数据
- 支持数千台机器的横向扩展
- 提供比GFS更丰富的数据模型
- 实现高性能的随机读写访问
核心需求场景:
- 网页索引:存储数十亿网页的属性
- Google Earth:管理海量地理图像数据
- 用户数据:Gmail、Google Analytics等
7.4.2 数据模型
Bigtable是一个稀疏的、分布式的、持久化的多维排序映射:
(row:string, column:string, time:int64) → string
示例:网页表
行键: com.cnn.www (反向URL)
列族: contents:, anchor:
时间戳: t1, t2, t3...
┌─────────────────┬──────────────────────┬─────────────────────────┐
│ Row Key │ contents:html │ anchor:cnnsi.com │
├─────────────────┼──────────────────────┼─────────────────────────┤
│ com.cnn.www │ t6: "<html>..." │ t9: "CNN" │
│ │ t5: "<html>..." │ t8: "CNN.com" │
│ │ t3: "<html>..." │ │
├─────────────────┼──────────────────────┼─────────────────────────┤
│ com.google.www │ t10: "<html>..." │ t11: "Google" │
└─────────────────┴──────────────────────┴─────────────────────────┘
数据模型特点:
-
行: - 行键是任意字符串(最大64KB) - 按字典序排序存储 - 行的读写是原子的
-
列族: - 列键格式:family:qualifier - 列族必须预先创建 - 同一列族的数据物理存储在一起
-
时间戳: - 64位整数,微秒精度 - 可自动分配或应用指定 - 用于版本控制和垃圾回收
7.4.3 系统架构
Bigtable架构
┌──────────────┐
│ 客户端 │
└──────┬───────┘
│
┌──────▼───────┐
│ Bigtable库 │
└──────┬───────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Master │ │ Chubby │ │ Tablet Server│
│ │◄───┤ │ │ │
│ ·分配tablets │ │ ·锁服务 │ │ ·管理tablets │
│ ·负载均衡 │ │ ·根tablet位置│ │ ·处理读写 │
│ ·垃圾回收 │ │ ·模式信息 │ │ ·分裂tablets │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ GFS │
│ 存储SSTable │
└──────────────┘
关键组件:
-
Master服务器(单点) - 分配tablets到tablet服务器 - 检测tablet服务器的加入和失效 - 负载均衡和垃圾回收 - 处理模式变更(创建表、列族)
-
Tablet服务器(数百到数千个) - 管理一组tablets(通常10-1000个) - 处理tablets的读写请求 - 在tablets过大时进行分裂
-
Chubby锁服务 - 确保任何时刻最多一个活跃的master - 存储Bigtable数据的引导位置 - 发现tablet服务器和终结它们 - 存储Bigtable模式信息
7.4.4 Tablet定位
使用三层结构定位tablet:
Tablet定位层次结构
┌─────────────────┐
│ Chubby │
│ (Root tablet │
│ location) │
└────────┬────────┘
│
┌────────▼────────┐
│ Root tablet │ (永不分裂,1个tablet)
│ METADATA表 │
└────────┬────────┘
│
┌────────▼────────┐
│ Other METADATA │ (可分裂,~2^34个tablets)
│ tablets │
└────────┬────────┘
│
┌────────▼────────┐
│ User tablets │
└─────────────────┘
定位计算:
- 128MB METADATA tablet
- 1KB per tablet元数据
- 可索引2^34个tablets
- 每个tablet 128MB
- 总计可存储2^61字节数据
7.4.5 Tablet服务
写入路径:
写入流程
1. 写入 ──► memtable (内存,已排序)
2. memtable满 ──► 冻结,创建新memtable
3. 冻结的memtable ──► 转换为SSTable
4. SSTable ──► 写入GFS
内存结构:
┌──────────────────────────────┐
│ Tablet Server内存 │
├──────────────────────────────┤
│ ┌────────────────────────┐ │
│ │ 提交日志 (WAL) │ │
│ └────────────────────────┘ │
│ ┌────────────────────────┐ │
│ │ memtable (活跃) │ │
│ └────────────────────────┘ │
│ ┌────────────────────────┐ │
│ │ memtable (冻结) │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
读取路径:
读取合并流程
1. 检查授权和格式
2. 读取数据源:
├─► memtable (最新)
├─► 冻结的memtables
└─► SSTable文件 (最旧)
3. 合并多个数据源的结果
4. 返回最终数据
7.4.6 压缩(Compaction)
压缩类型:
-
Minor Compaction - memtable → 新SSTable - 减少内存使用 - 缩短恢复时间
-
Merging Compaction - 多个SSTable + memtable → 新SSTable - 减少SSTable数量 - 提高读取性能
-
Major Compaction - 所有SSTable → 一个SSTable - 删除已标记删除的数据 - 回收存储空间
压缩过程示意
Minor Compaction:
memtable ──────────► SSTable_new
Merging Compaction:
SSTable_1 ─┐
SSTable_2 ─┼────► SSTable_merged
SSTable_3 ─┘
Major Compaction:
SSTable_1 ─┐
SSTable_2 ─┤
SSTable_3 ─┼────► SSTable_major (无删除标记)
SSTable_4 ─┤
SSTable_5 ─┘
7.4.7 性能优化
-
局部性群组(Locality Groups) - 将经常一起访问的列族分组 - 每个局部性群组生成独立的SSTable - 支持高效的列族扫描
-
压缩 - 两阶段压缩:Bentley-McIlroy + 快速压缩 - 典型压缩率:10:1 - 空间换时间的权衡
-
缓存
两级缓存结构
┌─────────────────────────────┐
│ Scan Cache │
│ (键值对缓存,高层次) │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ Block Cache │
│ (SSTable块缓存,低层次) │
└─────────────────────────────┘
- Bloom过滤器 - 减少不必要的SSTable访问 - 极大提高读取性能 - 空间开销小(每个键几个位)
7.4.8 实际应用案例
Google Analytics (2006年数据):
- 200个tablet服务器
- 存储约1PB数据
- 处理数百万个网站的分析数据
- 原始点击表:~200TB
- 摘要表:~20TB
Google Earth:
- 存储卫星图像数据
- 单表超过70TB
- 支持高并发的图像服务
个性化搜索:
- 存储用户搜索历史
- 支持快速的用户查询
- 数据复制到多个数据中心
7.4.9 Bigtable的影响与演进
开源实现:
- Apache HBase (2008):Hadoop生态系统的列族数据库
- Apache Cassandra (2008):Facebook开发,结合Bigtable和Dynamo
- Apache Accumulo (2011):NSA贡献,增强安全特性
对NoSQL运动的影响:
Bigtable影响图谱
Bigtable (2006)
│
┌──────────┼──────────┐
▼ ▼ ▼
HBase Cassandra Accumulo
│ │ │
▼ ▼ ▼
NoSQL运动 宽列存储 分布式KV
Google内部演进:从Bigtable到Spanner:
- Bigtable:最终一致性,单数据中心优化
- Megastore (2011):跨数据中心,同步复制
- Spanner (2012):全球分布式,强一致性
7.5 Borg:大规模集群管理的先驱
7.5.1 背景与动机
Borg是Google内部的大规模集群管理系统,从2003年开始开发,2007年全面部署。虽然论文直到2015年才发表,但Borg已经运行了Google几乎所有的服务超过十年。
设计目标:
- 管理数万台机器上的数十万个作业
- 高资源利用率(60-70%)
- 高可用性(99.99%)
- 简化用户的部署和管理
7.5.2 核心概念
工作负载抽象:
Borg作业层次结构
┌──────────────────────────────┐
│ Job(作业) │
│ - 名称、所有者、约束 │
│ - 任务数量 │
└──────────────┬───────────────┘
│包含
┌────────┼────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Task 1 │ │ Task 2 │ │ Task N │
│ - 资源 │ │ - 资源 │ │ - 资源 │
│ - 二进制 │ │ - 二进制 │ │ - 二进制 │
└──────────┘ └──────────┘ └──────────┘
资源模型:
- CPU:以核数计量(可以是分数)
- 内存:以字节计量
- 磁盘:空间和I/O带宽
- 网络:带宽
优先级和配额:
优先级层次(从高到低)
┌─────────────────────────────┐
│ Production (Prod) │ 监控、生产服务
├─────────────────────────────┤
│ Batch │ 批处理作业
├─────────────────────────────┤
│ Best-effort (BE) │ 测试、低优先级
└─────────────────────────────┘
7.5.3 系统架构
Borg系统架构
┌─────────────────┐
│ BorgMaster │
│ (主控节点) │
│ ┌─────────────┐ │
│ │ Scheduler │ │
│ └─────────────┘ │
└────────┬────────┘
│
┌────────┼────────┐
│ │ │
控制信息 状态更新 心跳
│ │ │
┌───────────▼────────▼────────▼───────────┐
│ Borglets │
│ (每台机器上的代理) │
├──────────────────────────────────────────┤
│ Machine 1 │ Machine 2 │ ... │Machine N│
│ ┌────────┐ │ ┌────────┐ │ │┌────────┐│
│ │Borglet │ │ │Borglet │ │ ││Borglet ││
│ └────────┘ │ └────────┘ │ │└────────┘│
│ Tasks │ Tasks │ │ Tasks │
└──────────────────────────────────────────┘
BorgMaster组件:
-
主BorgMaster进程(逻辑单点) - 处理客户端RPC(创建、更新、删除作业) - 管理系统中所有对象的状态 - 与Borglets通信
-
调度器 - 异步扫描待处理的任务 - 分配任务到机器 - 考虑约束和优先级
-
Paxos副本(5个) - 保证高可用性 - 存储持久化状态 - 自动故障切换
Borglet功能:
- 启动和停止任务
- 重启失败的任务
- 管理本地资源
- 报告机器状态
- 提供HTTP服务器用于调试
7.5.4 调度算法
两阶段调度:
调度流程
第一阶段:可行性检查
┌─────────────────────────────┐
│ 过滤不满足约束的机器: │
│ - 资源不足 │
│ - 属性不匹配 │
│ - 已有冲突任务 │
└──────────────┬──────────────┘
▼
第二阶段:评分和选择
┌─────────────────────────────┐
│ 对可行机器评分: │
│ - 最小化被抢占的任务 │
│ - 优选已有包的机器 │
│ - 分散故障域 │
│ - 负载均衡 │
└─────────────────────────────┘
优化技术:
-
等价类(Equivalence Classes) - 将相似的机器分组 - 减少调度计算量
-
松弛随机化 - 不总是选择最优机器 - 避免热点和竞争
-
优先级抢占 - 高优先级任务可以抢占低优先级 - 被抢占的任务进入待处理队列
7.5.5 资源隔离
Linux容器技术:
资源隔离层次
┌─────────────────────────────┐
│ 任务(应用代码) │
├─────────────────────────────┤
│ 容器运行时 │
│ (资源限制和隔离) │
├─────────────────────────────┤
│ cgroups │
│ - CPU配额 │
│ - 内存限制 │
│ - I/O带宽 │
├─────────────────────────────┤
│ Linux内核 │
└─────────────────────────────┘
隔离机制:
- CPU:使用CFS(完全公平调度器)配额
- 内存:硬限制,OOM killer
- 磁盘I/O:带宽限制
- 网络:流量整形
7.5.6 可用性设计
故障处理:
故障检测和恢复
┌────────────────────────────────┐
│ Borglet故障: │
│ - 继续运行已有任务 │
│ - 新任务等待恢复 │
│ - 超时后重新调度 │
├────────────────────────────────┤
│ BorgMaster故障: │
│ - Paxos选举新主 │
│ - 从检查点恢复状态 │
│ - 重新同步Borglets │
├────────────────────────────────┤
│ 任务故障: │
│ - 自动重启(有限次数) │
│ - 重新调度到其他机器 │
│ - 通知用户 │
└────────────────────────────────┘
可用性数据(2013年):
- BorgMaster平均故障间隔:~60天
- 故障切换时间:~10秒
- 99.99%的作业不受影响
7.5.7 使用效率
资源利用率提升技术:
-
混部(Co-location) - 将延迟敏感和批处理任务混合 - 利用负载的时间差异 - 典型利用率:60-70%
-
资源回收
资源回收机制
预留资源 ────► 实际使用 ────► 可回收资源
100% 60% 40%
│
▼
分配给BE任务
- 细粒度资源分配 - CPU可以0.1核为单位 - 内存以MB为单位 - 避免资源浪费
7.5.8 从Borg到Kubernetes
经验教训:
| 方面 | Borg | Kubernetes |
| 方面 | Borg | Kubernetes |
|---|---|---|
| API | 专有RPC | REST API |
| 配置 | BCL语言 | YAML/JSON |
| 网络 | IP-per-machine | IP-per-pod |
| 存储 | 自定义 | 标准卷抽象 |
| 生态 | 内部 | 开源社区 |
Kubernetes继承的核心概念:
- Pods ← Borg allocs
- Services ← Borg服务发现
- Labels ← Borg标签
- Namespaces ← Borg cells的简化
Borg到Kubernetes的演进
Borg (2003-2015) Kubernetes (2014-)
内部使用 开源项目
│ │
┌───────┼───────┐ ┌───────┼───────┐
▼ ▼ ▼ ▼ ▼ ▼
Omega 内部改进 经验积累 Docker CNCF 云原生
│ │
└──────────┬─────────────────┘
▼
行业标准容器编排平台