土法炼钢兴趣小组的算法知识备份

【系统架构设计百科】Google 基础设施:Borg、Spanner 与全球化架构

文章导航

分类入口
architecture
标签入口
#Google#Borg#Spanner#TrueTime#B4#global-infrastructure

目录

Google 在 2003 年发表 GFS 论文时,整个行业还在用单机数据库处理 Web 请求。此后二十年间,Google 内部基础设施经历了多次代际演进,从 GFS 到 Colossus,从 MapReduce 到 Flume,从 Borg 到 Kubernetes,从 Bigtable 到 Spanner。这些系统不仅支撑了 Google 自身的搜索、广告、YouTube 等核心业务,也深刻影响了整个分布式系统(Distributed Systems)领域的发展方向。本文将深入分析 Google 基础设施中最核心的几个系统,拆解其设计原理、工程实现与架构哲学。

一、Google 基础设施技术演进

Google 基础设施的演进可以划分为三个主要阶段,每个阶段都代表了分布式系统设计范式的重大转变。

1.1 第一阶段:三驾马车(2003-2006)

2003 至 2006 年间,Google 相继发表了三篇奠基性论文:

这三个系统构成了 Google 早期基础设施的基石。开源社区根据这三篇论文分别实现了 HDFS、Hadoop MapReduce 和 HBase,催生了整个大数据(Big Data)生态系统。

1.2 第二阶段:内部重构(2007-2012)

随着业务规模的持续增长,第一代系统的局限性逐渐暴露:

同期,Borg 系统在内部全面部署,成为 Google 所有服务的统一调度平台。

1.3 第三阶段:全球化架构(2012 至今)

第三阶段的核心特征是全球化部署(Global Deployment)和软件定义基础设施(Software-Defined Infrastructure):

下面的时间线展示了这些系统的演进关系:

timeline
    title Google 基础设施技术演进
    2003 : GFS 论文发表
    2004 : MapReduce 论文发表
    2006 : Bigtable 论文发表
         : Borg 内部全面部署
    2009 : Colossus 替代 GFS
    2010 : Megastore 发表
    2011 : Spanner 内部上线
    2012 : Spanner 论文发表
    2013 : B4 论文发表
         : F1 数据库论文发表
    2014 : Kubernetes 开源
         : BeyondCorp 论文发表
    2017 : Spanner 对外开放(Cloud Spanner)
    2020 : Borg 第三代架构

1.4 技术代际对比

维度 第一代(2003-2006) 第二代(2007-2012) 第三代(2012 至今)
文件系统 GFS(单 Master) Colossus(分布式 Master) Colossus D(自动分层)
计算框架 MapReduce(批处理) FlumeJava(优化批处理) Cloud Dataflow(流批统一)
数据库 Bigtable(NoSQL) Megastore(受限 SQL) Spanner(全功能 SQL)
调度系统 各业务独立调度 Borg(统一调度) Borg 3.0 + Autopilot
网络架构 传统 MPLS 网络 Watchtower SDN B4 + Jupiter + Andromeda
安全模型 边界防御 内部逐步加密 BeyondCorp 零信任
一致性模型 最终一致性 有限强一致性 全球强一致性

二、Borg:容器编排的鼻祖

Borg 是 Google 内部的大规模集群管理系统(Cluster Management System),自 2003 年开始开发,2006 年在全公司范围内部署。在 Borg 之前,Google 内部有多个独立的集群管理系统,包括 Babysitter 和 Global Work Queue。Borg 将这些系统统一,成为管理 Google 所有计算资源的核心平台。

2.1 Borg 架构概览

一个 Borg 集群(Cell)通常包含一万到数万台机器。每个 Cell 由一个逻辑上的 BorgMaster 和大量的 Borglet 组成。

graph TB
    subgraph BorgMaster["BorgMaster(5 副本 Paxos)"]
        BM1["主 BorgMaster 进程"]
        BM2["调度器(Scheduler)"]
        BM3["持久化存储(Paxos Store)"]
        BM1 --> BM2
        BM1 --> BM3
    end

    subgraph Cell["Borg Cell(数万台机器)"]
        subgraph Machine1["Machine 1"]
            B1["Borglet"]
            C1["Container A"]
            C2["Container B"]
            B1 --> C1
            B1 --> C2
        end
        subgraph Machine2["Machine 2"]
            B2["Borglet"]
            C3["Container C"]
            C4["Container D"]
            B2 --> C3
            B2 --> C4
        end
        subgraph Machine3["Machine N"]
            B3["Borglet"]
            C5["Container E"]
            C6["Container F"]
            B3 --> C5
            B3 --> C6
        end
    end

    BM1 -->|"状态同步"| B1
    BM1 -->|"状态同步"| B2
    BM1 -->|"状态同步"| B3

    User["用户 / BCL 配置"] -->|"提交 Job"| BM1

2.2 核心概念

Borg 中有两个核心抽象:

Borg 的 Job 分为两大类:

2.3 调度算法

Borg 的调度过程分为两个阶段:

可行性检查(Feasibility Checking):找出满足 Task 资源和约束要求的所有机器。

评分(Scoring):对可行机器进行打分,选择最优的机器。评分考虑的因素包括:

以下是一个简化的 Borg Job 配置示例,使用 BCL(Borg Configuration Language):

# Borg Job 配置示例(BCL 风格伪代码)
job = Job(
    name = "web-search-frontend",
    priority = PRODUCTION,
    instances = 500,

    # 资源需求
    requirements = Requirements(
        cpu = 2.0,          # 2 个 CPU 核心
        ram = 4 * GiB,      # 4 GiB 内存
        disk = 50 * GiB,    # 50 GiB 磁盘
    ),

    # 约束条件
    constraints = [
        # 分散在不同故障域
        Constraint(attribute="failure_domain", operator=SPREAD),
        # 只使用特定机型
        Constraint(attribute="machine_type", operator=IN, values=["n2-standard"]),
    ],

    # 容器配置
    container = Container(
        image = "//search/frontend:release-2026.04",
        ports = [Port(name="http", number=8080)],
        health_check = HealthCheck(
            path = "/healthz",
            interval = 10 * SECONDS,
            timeout = 3 * SECONDS,
        ),
    ),

    # 更新策略
    update_strategy = RollingUpdate(
        max_unavailable = 10,       # 最多 10 个实例同时不可用
        max_surge = 50,             # 最多额外启动 50 个实例
        canary_count = 5,           # 先更新 5 个金丝雀实例
        canary_duration = 300,      # 金丝雀观察 300 秒
    ),
)

2.4 资源回收与超卖

Borg 的一个关键设计是资源回收(Resource Reclamation)。用户在提交 Job 时通常会过度申请资源,导致大量资源被预留但未实际使用。Borg 通过监控 Task 的实际资源使用量,将未使用的资源回收并分配给低优先级任务。

这种机制使得 Google 的整体集群利用率远高于行业平均水平。据公开数据,Google 的集群 CPU 利用率在 60% 以上,而行业平均水平通常在 10%-20%。

# 资源回收算法伪代码
class ResourceReclaimer:
    def __init__(self, safety_margin: float = 0.1):
        self.safety_margin = safety_margin

    def calculate_reclaimable(self, task: Task) -> Resources:
        """计算可回收资源"""
        reserved = task.resource_request        # 申请量
        actual_peak = task.peak_usage(window=300)  # 过去 5 分钟峰值
        safe_usage = actual_peak * (1 + self.safety_margin)  # 安全用量

        reclaimable = Resources(
            cpu = max(0, reserved.cpu - safe_usage.cpu),
            ram = max(0, reserved.ram - safe_usage.ram),
        )
        return reclaimable

    def reclaim_for_cell(self, cell: Cell) -> Resources:
        """计算整个 Cell 的可回收资源"""
        total_reclaimable = Resources(cpu=0, ram=0)
        for machine in cell.machines:
            for task in machine.running_tasks:
                if task.priority >= PRODUCTION:
                    reclaimable = self.calculate_reclaimable(task)
                    total_reclaimable += reclaimable
        return total_reclaimable

2.5 Alloc 机制

Alloc(Allocation 的缩写)是 Borg 中一个独特的资源管理概念。Alloc 预留了一组机器上的资源,用户可以在 Alloc 内部运行多个 Task。这个概念后来演变为 Kubernetes 中的 Pod。

Alloc 的典型用途包括:

三、从 Borg 到 Kubernetes

2014 年,Google 将 Borg 的核心设计思想以 Kubernetes(K8s)的形式开源。Kubernetes 并非 Borg 的简单翻译,而是基于 Borg 十余年运行经验的重新设计。

3.1 核心概念映射

Borg 概念 Kubernetes 概念 关键区别
Cell Cluster K8s 集群规模通常小于 Borg Cell
Job Deployment / StatefulSet / Job K8s 将不同类型的工作负载拆分为多种资源类型
Task Pod Pod 可包含多个容器,对应 Borg 的 Alloc
Alloc Pod 概念直接对应
BorgMaster kube-apiserver + etcd + kube-scheduler + kube-controller-manager K8s 将 BorgMaster 的职责拆分为多个组件
Borglet kubelet 功能基本对应
优先级抢占 PriorityClass + Preemption K8s 的抢占机制更加标准化
BCL(配置语言) YAML / Helm / Kustomize K8s 采用声明式 YAML 配置
Naming(BNS) Service + DNS K8s 内置了服务发现机制

3.2 Borg 经验教训对 Kubernetes 的影响

Google 在 Borg 论文中总结了多条经验教训,这些教训直接影响了 Kubernetes 的设计:

教训一:Job 不应该是唯一的分组机制。

在 Borg 中,Job 是管理 Task 的唯一方式。但实际运维中,用户经常需要将属于不同 Job 的服务作为一个整体来管理。Kubernetes 引入了 Label 和 Selector 机制,允许用户通过任意键值对(Key-Value Pair)对资源进行灵活分组。

# Kubernetes Label 和 Selector 示例
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-search-frontend
  labels:
    app: web-search
    tier: frontend
    version: v2
    team: search-infra
spec:
  replicas: 500
  selector:
    matchLabels:
      app: web-search
      tier: frontend
  template:
    metadata:
      labels:
        app: web-search
        tier: frontend
        version: v2
    spec:
      containers:
      - name: search-frontend
        image: gcr.io/search/frontend:release-2026.04
        ports:
        - containerPort: 8080
          name: http
        resources:
          requests:
            cpu: "2"
            memory: "4Gi"
          limits:
            cpu: "4"
            memory: "8Gi"
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          periodSeconds: 10
          timeoutSeconds: 3

教训二:每个 Task 应该有自己的 IP 地址。

Borg 中的 Task 共享主机 IP,通过端口区分不同服务。这导致了端口冲突问题和复杂的端口管理。Kubernetes 为每个 Pod 分配独立的 IP 地址,消除了端口冲突。

教训三:需要更好的内省和调试工具。

Borg 的调试工具分散在多个系统中。Kubernetes 通过 kubectl、Dashboard 和标准化的 API 提供了统一的内省接口。

3.3 Kubernetes 超越 Borg 的设计

Kubernetes 在若干方面做出了超越 Borg 的设计决策:

# Kubernetes 声明式 API 示例:自定义资源定义(CRD)
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: spannerinstances.database.google.com
spec:
  group: database.google.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                displayName:
                  type: string
                nodeCount:
                  type: integer
                  minimum: 1
                config:
                  type: string
  scope: Namespaced
  names:
    plural: spannerinstances
    singular: spannerinstance
    kind: SpannerInstance

Kubernetes 的 CRD(Custom Resource Definition,自定义资源定义)机制是 Borg 中不存在的。它允许用户扩展 Kubernetes API,定义新的资源类型,使得 Kubernetes 从一个容器编排工具演变为一个通用的分布式系统控制平面(Control Plane)。

3.4 Autopilot:Borg 的自动化运维

Google 在 2020 年发表了 Autopilot 论文,描述了 Borg 中的自动化资源管理系统。Autopilot 通过机器学习(Machine Learning)预测 Task 的资源使用模式,自动调整资源配额,进一步提升集群利用率。

# Autopilot 资源推荐算法伪代码
class AutopilotRecommender:
    def __init__(self):
        self.model = load_ml_model("resource_predictor_v3")
        self.history_window = 7 * 24 * 3600  # 7 天历史数据

    def recommend(self, task_group: TaskGroup) -> ResourceRecommendation:
        """基于历史数据和 ML 模型推荐资源配置"""
        # 收集历史资源使用数据
        history = task_group.get_usage_history(self.history_window)

        # 提取特征
        features = self.extract_features(history)

        # ML 模型预测未来资源需求
        predicted_peak = self.model.predict(features)

        # 添加安全裕度
        recommendation = ResourceRecommendation(
            cpu = predicted_peak.cpu * 1.15,      # 15% 安全裕度
            memory = predicted_peak.memory * 1.20,  # 20% 安全裕度(OOM 代价更高)
            confidence = self.model.confidence(features),
        )

        return recommendation

    def extract_features(self, history: UsageHistory) -> Features:
        """从历史数据中提取特征"""
        return Features(
            p50_cpu = history.percentile(metric="cpu", p=50),
            p95_cpu = history.percentile(metric="cpu", p=95),
            p99_cpu = history.percentile(metric="cpu", p=99),
            p50_memory = history.percentile(metric="memory", p=50),
            p95_memory = history.percentile(metric="memory", p=95),
            p99_memory = history.percentile(metric="memory", p=99),
            diurnal_pattern = history.detect_diurnal_pattern(),
            growth_rate = history.linear_growth_rate(),
            task_count = history.avg_task_count(),
        )

四、Spanner:全球分布式数据库

Spanner 是 Google 研发的全球分布式数据库(Globally Distributed Database),于 2012 年发表论文。它是业界第一个在全球范围内提供外部一致性(External Consistency)的数据库系统,同时支持完整的 SQL 语义和 ACID 事务。

4.1 设计动机

Spanner 的研发源于 Google 内部对 Bigtable 和 Megastore 局限性的不满:

Spanner 的设计目标是:在全球范围内提供强一致性、高可用性和高性能的关系型数据库服务。

4.2 Spanner 整体架构

graph TB
    subgraph Client["客户端"]
        APP["应用程序"]
        CLib["Spanner 客户端库"]
        APP --> CLib
    end

    subgraph SpannerService["Spanner 服务"]
        subgraph Zone1["Zone A(美国东部)"]
            ZM1["Zone Master"]
            LS1["Location Proxy"]
            SS1["SpanServer 1"]
            SS2["SpanServer 2"]
            ZM1 --> SS1
            ZM1 --> SS2
        end

        subgraph Zone2["Zone B(欧洲西部)"]
            ZM2["Zone Master"]
            LS2["Location Proxy"]
            SS3["SpanServer 3"]
            SS4["SpanServer 4"]
            ZM2 --> SS3
            ZM2 --> SS4
        end

        subgraph Zone3["Zone C(亚太地区)"]
            ZM3["Zone Master"]
            LS3["Location Proxy"]
            SS5["SpanServer 5"]
            SS6["SpanServer 6"]
            ZM3 --> SS5
            ZM3 --> SS6
        end

        UM["Universe Master"]
        PM["Placement Driver"]
        UM --> ZM1
        UM --> ZM2
        UM --> ZM3
    end

    CLib --> LS1
    CLib --> LS2
    CLib --> LS3

Spanner 的架构包含以下核心组件:

4.3 SpanServer 内部架构

每个 SpanServer 管理多个 Tablet,每个 Tablet 的数据通过 Paxos 协议(Paxos Protocol)在多个 Zone 之间同步复制。

graph TB
    subgraph SpanServer["SpanServer"]
        subgraph Tablet1["Tablet"]
            PS["Paxos State Machine"]
            TM["Transaction Manager"]
            LS["Lock Table"]
            CS["Colossus Storage(SSTable)"]

            PS --> TM
            PS --> LS
            TM --> CS
        end
    end

    subgraph Replicas["Paxos 副本组"]
        R1["副本 1(Leader)"]
        R2["副本 2(Follower)"]
        R3["副本 3(Follower)"]
        R4["副本 4(Follower)"]
        R5["副本 5(Follower)"]
    end

    PS --> R1
    PS --> R2
    PS --> R3
    PS --> R4
    PS --> R5

每个 Paxos 副本组(Replica Group)通常包含 5 个副本,分布在至少 3 个不同的 Zone 中。Leader 副本负责处理写入请求和协调事务,Follower 副本提供读取服务。

4.4 数据模型

Spanner 的数据模型是带层次关系的关系表(Hierarchical Relational Tables)。子表的行在物理上与父表的对应行共同存储,这种设计显著降低了涉及父子关系的联合查询延迟。

-- Spanner 表定义示例(DDL)
CREATE TABLE Users (
    UserId     INT64 NOT NULL,
    Email      STRING(256) NOT NULL,
    Name       STRING(128),
    CreatedAt  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
) PRIMARY KEY (UserId);

-- Albums 是 Users 的子表,物理上交错存储
CREATE TABLE Albums (
    UserId     INT64 NOT NULL,
    AlbumId    INT64 NOT NULL,
    Title      STRING(256),
    CreatedAt  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
) PRIMARY KEY (UserId, AlbumId),
  INTERLEAVE IN PARENT Users ON DELETE CASCADE;

-- Photos 是 Albums 的子表
CREATE TABLE Photos (
    UserId     INT64 NOT NULL,
    AlbumId    INT64 NOT NULL,
    PhotoId    INT64 NOT NULL,
    FileName   STRING(512),
    FileSize   INT64,
    UploadedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
) PRIMARY KEY (UserId, AlbumId, PhotoId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

-- 二级索引
CREATE INDEX PhotosByUploadTime ON Photos(UploadedAt DESC);
CREATE UNIQUE INDEX UsersByEmail ON Users(Email);

这种交错存储(Interleaved Storage)意味着同一个 User 的所有 Album 和 Photo 数据在物理上相邻存储,查询 “某个用户的所有照片” 只需要一次顺序读取,而不需要跨分片的分布式查询。

五、TrueTime:原子钟与 GPS 的时间 API

TrueTime 是 Spanner 最具创新性的组件,也是 Spanner 能够实现外部一致性的基础。TrueTime 提供了一个全球一致的时间参考,其核心 API 只有三个方法。

5.1 TrueTime API

// TrueTime API 定义
type TrueTime interface {
    // Now 返回一个时间区间 [earliest, latest]
    // 保证真实时间在这个区间内
    Now() TTInterval

    // After 判断时间 t 是否确定已经过去
    After(t Timestamp) bool

    // Before 判断时间 t 是否确定还未到来
    Before(t Timestamp) bool
}

// TTInterval 表示一个时间不确定性区间
type TTInterval struct {
    Earliest Timestamp  // 真实时间不早于此
    Latest   Timestamp  // 真实时间不晚于此
}

// 不确定性范围 epsilon = (Latest - Earliest) / 2
// 通常 epsilon 在 1-7 毫秒之间,平均约 4 毫秒

TrueTime 与传统的 NTP(Network Time Protocol,网络时间协议)最大的区别在于:NTP 告诉你 “现在大约是几点”,而 TrueTime 告诉你 “现在的时间一定在这个区间内”。这个显式的不确定性区间(Uncertainty Interval)是 Spanner 实现外部一致性的关键。

5.2 TrueTime 的硬件基础

每个 Google 数据中心部署了多个 Time Master 服务器,分为两类:

# TrueTime 时间同步示例
class TrueTimeService:
    """TrueTime 客户端守护进程(每台机器运行一个)"""

    def __init__(self):
        self.time_masters = discover_time_masters()
        self.poll_interval = 30  # 每 30 秒轮询一次
        self.local_clock_drift_rate = 200e-6  # 200 ppm 晶振漂移率

    def now(self) -> TTInterval:
        """返回当前时间的不确定性区间"""
        local_time = read_local_clock()
        epsilon = self.calculate_epsilon()

        return TTInterval(
            earliest=local_time - epsilon,
            latest=local_time + epsilon,
        )

    def calculate_epsilon(self) -> Duration:
        """计算当前的时间不确定性"""
        time_since_last_sync = now() - self.last_sync_time
        drift_uncertainty = time_since_last_sync * self.local_clock_drift_rate
        sync_uncertainty = self.last_sync_epsilon  # 上次同步时的不确定性

        return sync_uncertainty + drift_uncertainty

    def sync_with_masters(self):
        """与多个 Time Master 同步"""
        readings = []
        for master in self.time_masters:
            rtt_start = read_local_clock()
            master_time = master.get_time()
            rtt_end = read_local_clock()
            rtt = rtt_end - rtt_start

            readings.append(TimeReading(
                master_time=master_time,
                rtt=rtt,
                uncertainty=rtt / 2,  # 网络延迟不确定性
            ))

        # 使用 Marzullo 算法选择最佳时间区间
        best_interval = marzullo_algorithm(readings)
        self.apply_correction(best_interval)
        self.last_sync_time = read_local_clock()
        self.last_sync_epsilon = best_interval.uncertainty

5.3 TrueTime 为何难以复制

TrueTime 之所以难以被其他公司复制,原因有以下几点:

维度 Google TrueTime 替代方案(如 CockroachDB HLC)
时间源 GPS + 原子钟 NTP 或本地时钟
不确定性范围 1-7 毫秒 250-500 毫秒
等待开销 事务提交等待约 7 毫秒 不等待,但降低一致性保证
硬件要求 专用 GPS 接收器 + 原子钟 无特殊硬件要求
部署成本 极高(数据中心级基础设施) 低(软件方案)
一致性保证 外部一致性(最强) 可线性化或因果一致性
时钟故障处理 硬件冗余 + 独立时间源 依赖 NTP,存在共模故障风险

CockroachDB 使用混合逻辑时钟(Hybrid Logical Clock,HLC)作为 TrueTime 的替代。HLC 结合了物理时钟和逻辑时钟,能够在不依赖特殊硬件的情况下提供因果一致性(Causal Consistency),但无法保证外部一致性。

5.4 Commit Wait 机制

Spanner 利用 TrueTime 实现外部一致性的核心机制是提交等待(Commit Wait)。当一个事务提交时,Spanner 为其分配一个提交时间戳(Commit Timestamp),然后等待直到 TrueTime 确认该时间戳已经过去。

// Commit Wait 机制伪代码
func (txn *Transaction) Commit() error {
    // 1. 获取 Paxos 多数派确认
    err := txn.paxosGroup.ReplicateWrite(txn.mutations)
    if err != nil {
        return err
    }

    // 2. 分配提交时间戳:取当前 TrueTime 区间的 latest 值
    commitTS := TrueTime.Now().Latest

    // 3. Commit Wait:等待直到 TrueTime 确认 commitTS 已经过去
    // 这保证了任何后续事务看到的时间一定晚于 commitTS
    for !TrueTime.After(commitTS) {
        sleep(1 * time.Millisecond)
    }

    // 4. 提交完成,通知客户端
    txn.status = COMMITTED
    txn.commitTimestamp = commitTS
    return nil
}

Commit Wait 的等待时间取决于 TrueTime 的不确定性范围(epsilon),通常在 7 毫秒左右。这个等待时间是 Spanner 为全球强一致性付出的核心延迟代价。

六、B4:软件定义广域网

B4 是 Google 的软件定义广域网(Software-Defined Wide Area Network,SD-WAN),连接 Google 在全球的数十个数据中心。B4 于 2013 年发表论文,是业界首个大规模部署的 SDN(Software-Defined Networking)广域网。

6.1 设计背景

Google 数据中心之间的广域网流量具有以下特征:

6.2 B4 架构

graph TB
    subgraph GlobalLayer["全局控制层"]
        TE["流量工程服务器(TE Server)"]
        BW["带宽分配器(Bandwidth Enforcer)"]
        GW["全局网关(Gateway)"]
        TE --> BW
    end

    subgraph Site1["数据中心 A"]
        SC1["站点控制器(Site Controller)"]
        OF1["OpenFlow 控制器"]
        SW1["交换机集群"]
        SC1 --> OF1
        OF1 --> SW1
    end

    subgraph Site2["数据中心 B"]
        SC2["站点控制器"]
        OF2["OpenFlow 控制器"]
        SW2["交换机集群"]
        SC2 --> OF2
        OF2 --> SW2
    end

    subgraph Site3["数据中心 C"]
        SC3["站点控制器"]
        OF3["OpenFlow 控制器"]
        SW3["交换机集群"]
        SC3 --> OF3
        OF3 --> SW3
    end

    TE --> SC1
    TE --> SC2
    TE --> SC3

    SW1 <-->|"WAN 链路"| SW2
    SW2 <-->|"WAN 链路"| SW3
    SW1 <-->|"WAN 链路"| SW3

B4 的架构分为三层:

6.3 流量工程算法

B4 的流量工程核心是一个带优先级的最大最小公平带宽分配算法(Max-Min Fair Bandwidth Allocation)。

Google 将广域网流量分为三个优先级:

  1. 最高优先级:面向用户的实时流量(如搜索请求的跨数据中心调用)
  2. 中等优先级:存储系统的数据复制流量(如 Spanner 的跨区域复制)
  3. 最低优先级:批处理数据传输(如数据备份、离线日志传输)
# B4 流量工程带宽分配伪代码
class TrafficEngineer:
    def __init__(self, topology: NetworkTopology):
        self.topology = topology

    def allocate_bandwidth(
        self, demands: list, link_capacities: dict
    ) -> dict:
        """基于优先级的最大最小公平分配"""
        allocations = {}
        remaining_capacity = dict(link_capacities)

        # 按优先级从高到低分配
        for priority in [HIGH, MEDIUM, LOW]:
            priority_demands = [
                d for d in demands if d.priority == priority
            ]

            # 为当前优先级的流量寻找最短路径
            for demand in priority_demands:
                paths = self.find_k_shortest_paths(
                    demand.src, demand.dst, k=3
                )

                # 在多条路径上分配带宽(ECMP 扩展)
                allocated = self.split_across_paths(
                    demand, paths, remaining_capacity
                )
                allocations[demand.id] = allocated

                # 更新剩余容量
                for path, bw in allocated.items():
                    for link in path.links:
                        remaining_capacity[link] -= bw

        return allocations

    def find_k_shortest_paths(self, src, dst, k=3):
        """使用 Yen 算法找到 K 条最短路径"""
        return yen_k_shortest_paths(self.topology, src, dst, k)

    def split_across_paths(self, demand, paths, capacity):
        """将流量需求分配到多条路径上"""
        total_available = sum(
            min(capacity[link] for link in p.links) for p in paths
        )
        allocated = {}

        for path in paths:
            path_capacity = min(capacity[link] for link in path.links)
            share = min(
                demand.bandwidth * (path_capacity / total_available),
                path_capacity,
            )
            allocated[path] = share

        return allocated

6.4 B4 的成效

B4 部署后取得了显著成效:

七、Colossus:下一代分布式文件系统

Colossus 是 GFS 的继任者,从 2009 年开始逐步替代 GFS。Colossus 解决了 GFS 的核心瓶颈:单主节点限制。

7.1 GFS 的局限性

GFS 的单 Master 架构在 Google 早期运行良好,但随着数据量的指数增长,单 Master 的内存成为瓶颈。GFS Master 需要在内存中维护所有文件的元数据,包括文件名到 Chunk 的映射关系。当文件数量达到数亿级别时,单台机器的内存无法容纳全部元数据。

7.2 Colossus 的架构改进

Colossus 的核心改进包括:

# Colossus 纠删码存储示例
class ColossusErasureCoding:
    """Reed-Solomon 纠删码配置"""

    def __init__(self, data_chunks: int = 6, parity_chunks: int = 3):
        self.data_chunks = data_chunks        # 数据块数量
        self.parity_chunks = parity_chunks    # 校验块数量
        self.total_chunks = data_chunks + parity_chunks
        # 存储开销 = 9/6 = 1.5 倍(对比三副本的 3 倍)

    def encode(self, data: bytes) -> list:
        """将数据编码为数据块 + 校验块"""
        chunk_size = len(data) // self.data_chunks
        data_blocks = [
            data[i * chunk_size:(i + 1) * chunk_size]
            for i in range(self.data_chunks)
        ]
        parity_blocks = reed_solomon_encode(
            data_blocks, self.parity_chunks
        )
        return data_blocks + parity_blocks

    def decode(self, available_chunks: dict) -> bytes:
        """只要有任意 6 个块(总共 9 个),即可恢复原始数据"""
        if len(available_chunks) < self.data_chunks:
            raise InsufficientChunksError(
                f"需要至少 {self.data_chunks} 个块,"
                f"当前只有 {len(available_chunks)} 个"
            )
        return reed_solomon_decode(
            available_chunks,
            self.data_chunks,
            self.parity_chunks,
        )

7.3 存储策略对比

策略 存储开销 可容忍故障数 恢复速度 读取性能 适用场景
三副本 3.0x 2 副本故障 快(直接复制) 高(任意副本可读) 热数据、低延迟场景
RS(6,3) 1.5x 3 块故障 中等(需要计算) 中等(需要多块并行读) 温数据、大容量场景
RS(10,4) 1.4x 4 块故障 较慢(需要更多计算) 较低(需要更多块并行读) 冷数据、归档场景

Colossus 会根据数据的访问频率自动选择存储策略。热数据使用三副本以获取最佳读取性能,数据冷却后自动转换为纠删码存储以节约成本。

八、Google 的安全基础设施(BeyondCorp)

BeyondCorp 是 Google 于 2014 年提出的零信任安全模型(Zero Trust Security Model)。传统的企业安全架构基于网络边界防御(Perimeter-based Security),假设内网是安全的。BeyondCorp 彻底抛弃了这一假设,认为内网和外网一样不可信。

8.1 BeyondCorp 的核心原则

  1. 网络位置不决定信任级别:无论用户在公司内部还是咖啡店,访问控制策略完全相同。
  2. 设备和用户的双重认证:每次访问不仅验证用户身份,还要验证设备的安全状态。
  3. 基于策略的细粒度访问控制:每个请求都要经过访问代理(Access Proxy)的策略检查。

8.2 BeyondCorp 架构

graph LR
    subgraph External["外部网络"]
        User1["员工(远程)"]
        User2["员工(办公室)"]
    end

    subgraph BeyondCorp["BeyondCorp 组件"]
        AP["访问代理(Access Proxy)"]
        AE["访问控制引擎(Access Engine)"]
        DI["设备清单(Device Inventory)"]
        UI["用户和组数据库"]
        TP["信任评估器(Trust Inferer)"]
        AP --> AE
        AE --> DI
        AE --> UI
        AE --> TP
    end

    subgraph Internal["内部服务"]
        S1["Gmail 后端"]
        S2["内部工具"]
        S3["代码仓库"]
    end

    User1 -->|"HTTPS"| AP
    User2 -->|"HTTPS"| AP
    AP --> S1
    AP --> S2
    AP --> S3

8.3 访问决策流程

# BeyondCorp 访问决策伪代码
class AccessEngine:
    def evaluate_access(self, request: AccessRequest) -> AccessDecision:
        """评估一次访问请求"""
        # 1. 验证用户身份
        user = self.authenticate_user(request.credentials)
        if not user:
            return AccessDecision.DENY("身份验证失败")

        # 2. 验证设备状态
        device = self.device_inventory.get(request.device_id)
        if not device:
            return AccessDecision.DENY("未知设备")

        # 3. 计算信任分数
        trust_score = self.trust_inferer.compute(user, device)

        # 4. 检查策略
        policy = self.get_policy(request.target_service)
        required_trust = policy.minimum_trust_level

        if trust_score >= required_trust:
            return AccessDecision.ALLOW(
                trust_level=trust_score,
                audit_log=True,
            )
        elif trust_score >= required_trust * 0.8:
            # 信任分数接近阈值,要求额外验证
            return AccessDecision.REQUIRE_MFA(
                reason="信任分数低于阈值",
                trust_level=trust_score,
            )
        else:
            return AccessDecision.DENY(
                reason=f"信任分数 {trust_score} 低于要求 {required_trust}"
            )

    def compute_trust_score(self, user, device) -> float:
        """计算综合信任分数"""
        score = 0.0

        # 设备因素
        if device.is_corp_managed:
            score += 0.3
        if device.disk_encrypted:
            score += 0.1
        if device.os_up_to_date:
            score += 0.1
        if device.screen_lock_enabled:
            score += 0.05

        # 用户因素
        if user.mfa_enabled:
            score += 0.2
        if user.recent_security_training:
            score += 0.05
        if not user.has_security_incidents:
            score += 0.1

        # 行为因素
        if self.is_normal_access_pattern(user):
            score += 0.1

        return min(score, 1.0)

8.4 从 VPN 到零信任的迁移

Google 的 BeyondCorp 迁移历时数年,关键步骤包括:

  1. 建立完整的设备清单数据库,追踪每台设备的安全状态
  2. 将所有内部服务迁移到访问代理后方
  3. 逐步收紧访问策略,最终完全取消 VPN
  4. 持续监控和调整信任评估模型

这一模型后来被业界广泛采纳,成为企业安全架构的新范式。

九、工程案例:Spanner 的全球一致性读写

本节通过一个完整的工程案例,详细分析 Spanner 如何在全球范围内实现强一致性的读写操作。

9.1 场景描述

某全球电商平台使用 Spanner 存储用户订单数据。系统部署在三个大洲的五个数据中心:美国东部、美国西部、欧洲西部、亚太东北、亚太东南。一个用户在东京下单,购买商品的同时需要扣减库存,两个操作必须在一个事务中原子完成。

9.2 数据库设计

-- 订单系统 Spanner 表设计
CREATE TABLE Inventory (
    ProductId    INT64 NOT NULL,
    RegionCode   STRING(16) NOT NULL,
    Quantity     INT64 NOT NULL,
    LastUpdated  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
) PRIMARY KEY (ProductId, RegionCode);

CREATE TABLE Orders (
    OrderId      STRING(36) NOT NULL,
    UserId       INT64 NOT NULL,
    CreatedAt    TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
    Status       STRING(32) NOT NULL,
    TotalAmount  FLOAT64 NOT NULL,
) PRIMARY KEY (OrderId);

CREATE TABLE OrderItems (
    OrderId      STRING(36) NOT NULL,
    ItemId       INT64 NOT NULL,
    ProductId    INT64 NOT NULL,
    Quantity     INT64 NOT NULL,
    UnitPrice    FLOAT64 NOT NULL,
) PRIMARY KEY (OrderId, ItemId),
  INTERLEAVE IN PARENT Orders ON DELETE CASCADE;

CREATE INDEX OrdersByUser ON Orders(UserId, CreatedAt DESC);

9.3 读写事务实现

// Spanner 全局一致性事务示例(Java)
import com.google.cloud.spanner.*;

public class OrderService {
    private final DatabaseClient dbClient;

    public OrderService(DatabaseClient dbClient) {
        this.dbClient = dbClient;
    }

    /**
     * 创建订单并扣减库存
     * 这是一个跨分片的读写事务(Read-Write Transaction
     */
    public Order createOrder(String userId, List<OrderItem> items) {
        return dbClient
            .readWriteTransaction()
            .run(transaction -> {
                String orderId = generateOrderId();
                double totalAmount = 0;

                for (OrderItem item : items) {
                    // 1. 读取当前库存(强一致性读)
                    Struct row = transaction.readRow(
                        "Inventory",
                        Key.of(item.getProductId(), "APAC-NE"),
                        Arrays.asList("Quantity", "LastUpdated")
                    );

                    if (row == null) {
                        throw new SpannerException(
                            ErrorCode.NOT_FOUND,
                            "商品不存在: " + item.getProductId()
                        );
                    }

                    long currentQuantity = row.getLong("Quantity");

                    // 2. 检查库存是否充足
                    if (currentQuantity < item.getQuantity()) {
                        throw new SpannerException(
                            ErrorCode.ABORTED,
                            "库存不足: " + item.getProductId()
                            + " 需要 " + item.getQuantity()
                            + " 实际 " + currentQuantity
                        );
                    }

                    // 3. 扣减库存
                    transaction.buffer(
                        Mutation.newUpdateBuilder("Inventory")
                            .set("ProductId").to(item.getProductId())
                            .set("RegionCode").to("APAC-NE")
                            .set("Quantity")
                            .to(currentQuantity - item.getQuantity())
                            .set("LastUpdated")
                            .to(Value.COMMIT_TIMESTAMP)
                            .build()
                    );

                    totalAmount += item.getUnitPrice() * item.getQuantity();

                    // 4. 插入订单项
                    transaction.buffer(
                        Mutation.newInsertBuilder("OrderItems")
                            .set("OrderId").to(orderId)
                            .set("ItemId").to(item.getItemId())
                            .set("ProductId").to(item.getProductId())
                            .set("Quantity").to(item.getQuantity())
                            .set("UnitPrice").to(item.getUnitPrice())
                            .build()
                    );
                }

                // 5. 插入订单主记录
                transaction.buffer(
                    Mutation.newInsertBuilder("Orders")
                        .set("OrderId").to(orderId)
                        .set("UserId").to(Long.parseLong(userId))
                        .set("CreatedAt").to(Value.COMMIT_TIMESTAMP)
                        .set("Status").to("CREATED")
                        .set("TotalAmount").to(totalAmount)
                        .build()
                );

                return new Order(orderId, totalAmount);
            });
    }

    /**
     * 查询用户订单(强一致性读取)
     * 无论用户在哪个地区发起请求,
     * 都能看到最新提交的数据
     */
    public List<Order> getUserOrders(long userId) {
        // 使用强一致性读取(Strong Read)
        // Spanner 保证返回的数据反映了截至此刻所有已提交的事务
        try (ResultSet rs = dbClient
            .singleUse()  // 默认使用强一致性
            .executeQuery(
                Statement.newBuilder(
                    "SELECT o.OrderId, o.CreatedAt, o.Status, "
                    + "o.TotalAmount, oi.ProductId, oi.Quantity "
                    + "FROM Orders o "
                    + "JOIN OrderItems oi ON o.OrderId = oi.OrderId "
                    + "WHERE o.UserId = @userId "
                    + "ORDER BY o.CreatedAt DESC "
                    + "LIMIT 50"
                )
                .bind("userId").to(userId)
                .build()
            )) {
            List<Order> orders = new ArrayList<>();
            while (rs.next()) {
                orders.add(mapToOrder(rs));
            }
            return orders;
        }
    }

    /**
     * 查询用户订单(过期读取,降低延迟)
     * 允许读取最多 15 秒前的数据快照
     */
    public List<Order> getUserOrdersStale(long userId) {
        // 使用过期读取(Stale Read),最多容忍 15 秒的数据延迟
        // 好处:可以从最近的副本读取,显著降低跨区域延迟
        TimestampBound staleness = TimestampBound.ofMaxStaleness(
            15, TimeUnit.SECONDS
        );

        try (ResultSet rs = dbClient
            .singleUse(staleness)
            .executeQuery(
                Statement.newBuilder(
                    "SELECT OrderId, CreatedAt, Status, TotalAmount "
                    + "FROM Orders "
                    + "WHERE UserId = @userId "
                    + "ORDER BY CreatedAt DESC "
                    + "LIMIT 50"
                )
                .bind("userId").to(userId)
                .build()
            )) {
            List<Order> orders = new ArrayList<>();
            while (rs.next()) {
                orders.add(mapToOrder(rs));
            }
            return orders;
        }
    }

    private String generateOrderId() {
        return java.util.UUID.randomUUID().toString();
    }

    private Order mapToOrder(ResultSet rs) {
        return new Order(
            rs.getString("OrderId"),
            rs.getDouble("TotalAmount")
        );
    }
}

9.4 事务执行流程分析

当东京的用户发起下单请求时,Spanner 内部的执行流程如下:

sequenceDiagram
    participant Client as 客户端(东京)
    participant Leader as Paxos Leader(美东)
    participant F1 as Follower 1(美西)
    participant F2 as Follower 2(欧洲)
    participant F3 as Follower 3(亚太东北)
    participant F4 as Follower 4(亚太东南)
    participant TT as TrueTime

    Client->>Leader: 开始读写事务
    Leader->>Leader: 获取读锁,读取库存
    Leader-->>Client: 返回当前库存值

    Client->>Leader: 提交写入(扣减库存 + 创建订单)
    Leader->>TT: 获取当前时间区间
    TT-->>Leader: [earliest, latest]
    Leader->>Leader: 分配 commitTS = latest

    par Paxos 复制
        Leader->>F1: Prepare + Accept
        Leader->>F2: Prepare + Accept
        Leader->>F3: Prepare + Accept
        Leader->>F4: Prepare + Accept
    end

    F1-->>Leader: Accept OK
    F3-->>Leader: Accept OK
    Note over Leader: 收到多数派确认(3/5)

    Leader->>TT: 等待直到 After(commitTS)
    Note over Leader: Commit Wait(约 7ms)
    TT-->>Leader: commitTS 已过去

    Leader-->>Client: 事务提交成功
    Leader->>F2: 异步通知提交
    Leader->>F4: 异步通知提交

关键延迟分析:

阶段 延迟估算 说明
客户端到 Leader 约 150 毫秒 东京到美国东部的网络延迟
读取操作 约 5 毫秒 Leader 本地读取
Paxos 复制 约 80 毫秒 等待多数派确认,取决于最近的 Follower
Commit Wait 约 7 毫秒 等待 TrueTime 确认时间戳
响应返回 约 150 毫秒 美国东部到东京的网络延迟
总延迟 约 392 毫秒 端到端事务延迟

9.5 优化策略

为了降低跨区域事务的延迟,Google 提供了多种优化策略:

策略一:就近 Leader 选举

Spanner 允许用户配置 Leader 副本的首选区域。如果大部分写入来自亚太地区,可以将 Leader 配置在亚太的 Zone 中,减少客户端到 Leader 的延迟。

# Spanner 实例配置示例
instance_config:
  name: "asia-leader-config"
  replicas:
    - location: asia-northeast1    # 东京(Leader 候选)
      type: READ_WRITE
      default_leader: true
    - location: asia-southeast1    # 新加坡
      type: READ_WRITE
    - location: us-central1        # 爱荷华
      type: READ_WRITE
    - location: europe-west1       # 比利时
      type: READ_ONLY
    - location: us-east1           # 弗吉尼亚
      type: READ_ONLY

策略二:过期读取

对于不要求最新数据的读取操作,使用过期读取(Stale Read)可以从最近的副本读取,避免跨区域的 Leader 通信。

策略三:分区亲和性

将相关数据放在同一个分区(Partition),确保事务尽量在单个 Paxos 组内完成,避免两阶段提交(2PC,Two-Phase Commit)的额外延迟。

9.6 一致性读写的性能权衡

读取模式 一致性级别 延迟 适用场景
强一致性读 外部一致性 高(需要联系 Leader) 余额查询、库存检查
有界过期读 有界过期一致性 中等(就近副本) 商品列表、用户信息展示
精确时间戳读 快照一致性 低(就近副本) 数据分析、报表生成

十、Google 基础设施的设计哲学

通过分析 Google 二十年来的基础设施演进,可以提炼出以下核心设计哲学。

10.1 拥抱故障,而非预防故障

Google 的系统设计从不假设硬件是可靠的。从 GFS 到 Spanner,每个系统都将硬件故障视为常态事件(Normal Event),在软件层面实现容错。

这种理念体现在多个方面:

10.2 用软件解决硬件问题

Google 是 “软件定义一切”(Software-Defined Everything)的先驱。B4 用软件控制网络路由,TrueTime 用软件弥合时钟不确定性,Colossus 用软件管理存储策略。

这种方法的好处是:

10.3 先论文,后开源

Google 有一个独特的技术传播路径:先在内部打磨系统数年,然后发表学术论文描述设计原理,最后通过开源项目将核心理念输出给社区。

内部系统 论文 开源项目
GFS SOSP 2003 HDFS
MapReduce OSDI 2004 Hadoop MapReduce
Bigtable OSDI 2006 HBase
Chubby OSDI 2006 ZooKeeper
Borg EuroSys 2015 Kubernetes
Dremel VLDB 2010 Apache Drill / Presto
Spanner OSDI 2012 CockroachDB(社区实现)
Monarch VLDB 2020 无直接开源对应

10.4 全局优化优于局部优化

Google 的系统设计始终追求全局最优,而非局部最优:

10.5 总结

Google 基础设施的核心竞争力并非某个单一系统的技术突破,而是一整套协同运作的基础设施栈(Infrastructure Stack)。从最底层的硬件(定制服务器、定制网络设备、原子钟),到操作系统层(容器、Borg 调度),到存储层(Colossus、Spanner),到网络层(B4、Jupiter),再到安全层(BeyondCorp),每一层都紧密配合。

这种端到端的垂直整合(Vertical Integration)使得 Google 能够在每个层次之间进行跨层优化,实现 1+1 远大于 2 的效果。对于其他公司而言,复制 Google 的某个单一系统(如 Spanner)是可能的,但复制整个基础设施栈的协同效应,则几乎是不可能的任务。

参考资料

  1. Ghemawat, S., Gobioff, H., & Leung, S. (2003). The Google File System. SOSP 2003.
  2. Dean, J., & Ghemawat, S. (2004). MapReduce: Simplified Data Processing on Large Clusters. OSDI 2004.
  3. Chang, F., et al. (2006). Bigtable: A Distributed Storage System for Structured Data. OSDI 2006.
  4. Corbett, J. C., et al. (2012). Spanner: Google’s Globally-Distributed Database. OSDI 2012.
  5. Verma, A., et al. (2015). Large-scale Cluster Management at Google with Borg. EuroSys 2015.
  6. Jain, S., et al. (2013). B4: Experience with a Globally-Deployed Software Defined WAN. SIGCOMM 2013.
  7. Ward, M., & Barclay, R. (2014). BeyondCorp: A New Approach to Enterprise Security. Google White Paper.
  8. Rzadca, K., et al. (2020). Autopilot: Workload Autoscaling at Google Scale. EuroSys 2020.
  9. Burns, B., et al. (2016). Borg, Omega, and Kubernetes. ACM Queue, 14(1).
  10. Bacon, D. F., et al. (2017). Spanner: Becoming a SQL System. SIGMOD 2017.
  11. McKusick, M. K., & Quinlan, S. (2009). GFS: Evolution on Fast-forward. ACM Queue, 7(7).
  12. Singh, A., et al. (2015). Jupiter Rising: A Decade of Clos Topologies and Centralized Control in Google’s Datacenter Network. SIGCOMM 2015.
  13. Shute, J., et al. (2013). F1: A Distributed SQL Database That Scales. VLDB 2013.
  14. Burrows, M. (2006). The Chubby Lock Service for Loosely-Coupled Distributed Systems. OSDI 2006.

上一篇:阿里巴巴架构

下一篇:Slack 架构

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-13

【分布式系统百科】Spanner 与 TrueTime:用硬件解决分布式时间问题

在分布式系统中,时间一直是一个令人头疼的问题。不同机器的时钟会产生漂移,网络延迟让时间同步变得困难,而许多一致性协议又严重依赖时间排序。Google 的 Spanner 系统通过一个大胆的想法打破了这个困境:既然软件无法完美解决时间问题,为什么不用硬件来解决?TrueTime API 就是这个思路的产物——通过 GPS…

2026-04-13 · architecture

【系统架构设计百科】架构质量属性:不只是"高可用高性能"

需求评审时写下的'高可用、高性能、高并发',到了架构设计阶段几乎无法落地——因为它们不是可执行的需求。本文从 SEI/CMU 的质量属性理论出发,用 stimulus-response 场景模型把模糊需求变成可量化、可验证的架构约束,并拆解属性之间的冲突与联动关系。

2026-04-13 · architecture

【系统架构设计百科】告警策略:如何避免"狼来了"

大多数团队的告警系统都在制造噪声而不是传递信号。阈值告警看似直观,实则产生大量误报和漏报,值班工程师在凌晨三点被叫醒,却发现只是一次无害的毛刺。本文从告警疲劳的工业数据出发,拆解基于 SLO 的多窗口燃烧率告警算法,深入 Alertmanager 的路由、抑制与分组机制,结合 PagerDuty 的告警疲劳研究和真实工程案例,给出一套可落地的告警策略设计方法。

2026-04-13 · architecture

【系统架构设计百科】复杂性管理:架构的核心战场

系统复杂性是架构腐化的根源——本文从 Brooks 的本质复杂性与偶然复杂性划分出发,结合认知负荷理论与 Parnas 的信息隐藏原则,系统阐述复杂性的来源、度量与控制手段,并给出可操作的架构策略


By .