Azure Local / Storage Spaces Direct 五层数据布局原理深度剖析

未经同意,请勿转载!

副标题:Slab、Column、Region、Fault Domain------微软真正的存储语义

适用版本:Azure Local 2506 / 2510 / Windows Server 2025(含 23H2 / 22H2 沿用)

文档定位:架构白皮书深度。读完后应能在不查文档的前提下,复述 S2D 内部数据单位与跨节点条带化逻辑。

写作约定 :本文严格区分微软官方硬性表述微软隐含 / 默认行为基于已公开文档的推断。不写"必须/一定/官方禁止"等未经原文支持的绝对化措辞。


目录

  • [0. 引言:为什么官方文档看起来"很简单"](#0. 引言:为什么官方文档看起来"很简单")
  • [第 1 层:64 MiB Region 与 Slab------真正的数据单位](#第 1 层:64 MiB Region 与 Slab——真正的数据单位)
  • [第 2 层:Column------故障域感知的条带](#第 2 层:Column——故障域感知的条带)
  • [第 3 层:Mirror 的数据块分配](#第 3 层:Mirror 的数据块分配)
  • [第 4 层:Parity 的数据块分配](#第 4 层:Parity 的数据块分配)
  • [第 5 层:Fault Domain 多级保护](#第 5 层:Fault Domain 多级保护)
  • [6. Slab 大小估算(实战)](#6. Slab 大小估算(实战))
  • [7. Repair 路径详解](#7. Repair 路径详解)
  • [8. 与 Ceph / vSAN / 传统 RAID 的对比](#8. 与 Ceph / vSAN / 传统 RAID 的对比)
  • [9. 总结](#9. 总结)

0. 引言:为什么官方文档看起来"很简单"

如果只看微软官方文档对 Mirror(镜像)Parity(奇偶校验)Mirror-Accelerated Parity(MAP) 的描述,会觉得它和传统 RAID 区别不大------

  • Mirror = 数据多副本
  • Parity = 数据 + 校验位
  • MAP = Mirror 与 Parity 混合

但事实上,Storage Spaces Direct(S2D)以及 Azure Local 的底层已经远远不是传统 RAID5/RAID6,而更接近 Scale-Out Software Defined Storage(SDS,例如 Ceph、vSAN、Google Colossus)

理解它,需要分五层来看:

数据单位 谁来管理
第 1 层 64 MiB Region(ReFS MAP 迁移单位) ReFS
第 2 层 Slab(Storage Spaces 内部映射单位) Storage Spaces
第 3 层 Column(跨物理盘的条带位置) Storage Spaces
第 4 层 Fault Domain(Disk / Node / Enclosure / Rack) Storage Spaces + Azure Local RAC
第 5 层 Physical Disk 硬件

下面逐层展开。


第 1 层:64 MiB Region 与 Slab------真正的数据单位

1.1 大多数人的"直觉模型"

很多人会按下面的层次理解存储栈:

复制代码
VM
 └─ VHDX
     └─ NTFS / ReFS
         └─ Block (4 KiB)
             └─ Disk

进入 Storage Spaces 之后,这条链被重新切分:

复制代码
VM
 └─ VHDX
     └─ ReFS
         └─ 64 MiB Region         ← ReFS MAP 迁移单位
             └─ Slab              ← Storage Spaces 内部映射单位
                 └─ Column        ← 跨物理盘的条带位置
                     └─ Physical Disk

1.2 两个关键单位

(1) 64 MiB Region
  • ReFS 的 Mirror-Accelerated Parity(MAP) 在 Mirror 与 Parity 之间迁移数据时,以 64 MiB 为最小迁移单位
  • 这是 ReFS 文件系统层面的概念,与"Block(4 KiB / 64 KiB)"完全不同。
  • 它把一个 VHDX 在逻辑上划分为许多 64 MiB 的"区块",每个区块独立决定当前是放在 Mirror 还是 Parity 上。
(2) Slab
  • Storage Spaces 内部把每个 64 MiB Region 再切分成大量 Slab
  • Slab 是 Storage Spaces 的内部映射单位;Metadata 保存每个 Slab 位于哪些 Physical Extent
  • Slab 数量与大小取决于 Pool 中物理磁盘的容量与配置;微软公开文档未给出固定的 Slab 大小常数,因为它会随盘数、容量自动调整。

1.3 Region 与 Slab 的关系(示意)

复制代码
flowchart TD
    A[VHDX<br/>虚拟磁盘] --> B[ReFS 视角<br/>64 MiB Region 序列]
    B --> C[Region 0]
    B --> D[Region 1]
    B --> E[Region 2]
    B --> F[Region N]
    C --> G[Slab 0]
    C --> H[Slab 1]
    C --> I[Slab 2]
    C --> J[Slab K]
    G --> K[Physical Extent<br/>Column 选取]
    H --> L[Physical Extent<br/>Column 选取]
    I --> M[Physical Extent<br/>Column 选取]
    J --> N[Physical Extent<br/>Column 选取]

    style A fill:#e1f5ff
    style B fill:#fff3cd
    style G fill:#d4edda
    style K fill:#f8d7da

1.4 微软真正管理的是什么

把这两层串起来:

复制代码
64 MiB Region(ReFS 视角)
   ↓ 切分
多个 Slab(Storage Spaces 视角)
   ↓ 元数据记录
每个 Slab 放在哪几个 Column

也就是说,微软管理的不是文件、不是 Block、不是 VHDX,而是"Slab 物理位置表"

文件、目录、Block 在这个层面已经被 ReFS 接管。Storage Spaces 只关心:

"64 MiB Region 的第 N 个 Slab,现在物理上落在哪些 Column 的哪些盘上?副本是否满足?是否需要 Repair?"

这一点是后续理解 Mirror / Parity / MAP 的基础------所有的副本轮转、双校验布局、MAP Region Rotation,全部发生在 Slab 这一层,而不是 Block 层


第 2 层:Column------故障域感知的条带

2.1 一个常见的误解

很多人会直觉地认为:

Node = 一列(Column)

这是完全错误的。

微软定义的 Column 是:

一条 Stripe 在多少块物理盘上展开。

换句话说,Column 数 = 一个 Stripe 跨多少块物理盘,而不是跨多少 Node。

2.2 举例:4 Node × 2 SSD 拓扑

假设集群有 4 个 Node,每个 Node 直连 2 块 SSD:

复制代码
Node1 ── SSD1, SSD2
Node2 ── SSD1, SSD2
Node3 ── SSD1, SSD2
Node4 ── SSD1, SSD2

如果系统分配 Columns = 4,意味着一个 Stripe 展开在 4 块物理盘上:

复制代码
Stripe N ── DiskA | DiskB | DiskC | DiskD

而不是:

复制代码
Stripe N ── Node1 | Node2 | Node3 | Node4   ← 这是错误理解

2.3 Column 的选取流程

复制代码
flowchart TD
    Start[需要写入一个 Stripe] --> FD[收集故障域信息]
    FD --> Disk[Disk Fault Domain]
    FD --> Node[Node Fault Domain]
    FD --> Enc[Enclosure Fault Domain]
    FD --> Rack[Rack Fault Domain<br/>Azure Local RAC]
    Disk --> Alg[Column 选取算法]
    Node --> Alg
    Enc --> Alg
    Rack --> Alg
    Alg --> Filter[过滤:同 FD 的盘排除]
    Filter --> Score[为每个候选盘打分<br/>负载 / 容量 / 历史]
    Score --> Pick[挑出 N 个 Column]
    Pick --> Write[并发写入]

    style Alg fill:#fff3cd
    style Pick fill:#d4edda

2.4 一个关键推论

Node 数 ≠ Column 数。

场景 Node 数 推荐 Columns 说明
4 Node × 4 SSD 4 4 / 8 Columns 可等于盘数,也可大于 Node 数
4 Node × 8 SSD 4 8 跨 8 块盘条带
16 Node × 4 SSD 16 16 / 32 RAC 场景 Columns 受 Rack 约束

微软官方文档在描述 Mirror / Parity 容量效率时,会直接给"假设 Columns = N"这样的前提;这与 Node 数不是同一回事。


第 3 层:Mirror 的数据块如何分配

3.1 拓扑假设

为便于说明,本文接下来默认:

  • 4 Node × 多 SSD
  • 3-way Mirror

3.2 直觉会犯的错

很多人会以为写入一个 64 KiB Block 时:

复制代码
Node1 ── Data A
Node2 ── Data A 副本
Node3 ── Data A 副本

固定三个 Node。

实际并不是这样

3.3 真实流程:Column Set + 轮转

微软首先计算 Column Set------三个互不共享 Fault Domain 的物理盘位置,例如:

复制代码
Column0 ── Node1 Disk2
Column1 ── Node2 Disk1
Column2 ── Node4 Disk2

然后:

复制代码
Data A
  Copy1 → Column0 (Node1 Disk2)
  Copy2 → Column1 (Node2 Disk1)
  Copy3 → Column2 (Node4 Disk2)

下一块 Data B

复制代码
Data B
  Copy1 → Node3 Disk1
  Copy2 → Node1 Disk1
  Copy3 → Node2 Disk2

再下一块 Data C

复制代码
Data C
  Copy1 → Node4 Disk1
  Copy2 → Node3 Disk2
  Copy3 → Node1 Disk2

副本位置始终轮转,Column Set 每条 Stripe 都不一样。

3.4 副本轮转的图示

复制代码
flowchart LR
    subgraph Stripe1[S1]
        A1[Data A] --> N1D2[Node1 Disk2]
        A1 --> N2D1[Node2 Disk1]
        A1 --> N4D2[Node4 Disk2]
    end

    subgraph Stripe2[S2]
        A2[Data B] --> N3D1[Node3 Disk1]
        A2 --> N1D1[Node1 Disk1]
        A2 --> N2D2[Node2 Disk2]
    end

    subgraph Stripe3[S3]
        A3[Data C] --> N4D1[Node4 Disk1]
        A3 --> N3D2[Node3 Disk2]
        A3 --> N1D2b[Node1 Disk2]
    end

    style Stripe1 fill:#e1f5ff
    style Stripe2 fill:#fff3cd
    style Stripe3 fill:#d4edda

3.5 轮转的目的

  • 热点均衡:同一个 Node 不会被连续多个 Stripe "砸中"
  • Node IO 均衡:每个 Node 上的盘轮流承担写副本
  • Disk IO 均衡:避免某一块 SSD 成为写瓶颈

微软将这种机制称为 Distributed Mirror Placement------分布式副本放置。

关键认知:Mirror 不是"按 Node 固定副本位置",而是"按 Column Set 轮转副本位置"。这是 S2D 区别于传统 RAID1(固定盘对)最大的算法差异之一。


第 4 层:Parity 的数据块如何分配

4.1 Parity 比 Mirror 复杂

Parity(奇偶校验)的难点在于:校验位本身也是数据,必须参与轮转,否则校验盘会成为热点。

4.2 拓扑假设

  • 4 Node
  • Dual Parity(双校验,P + Q)

一个 Stripe 假设为:

复制代码
D1  D2  P  Q

4.3 直觉会犯的错

很多人会以为:

复制代码
Node1 ── D1
Node2 ── D2
Node3 ── P
Node4 ── Q

一直固定。

4.4 真实流程:P、Q 也轮转

复制代码
Stripe1:
  Node1 ── D1
  Node2 ── D2
  Node3 ── P
  Node4 ── Q

Stripe2:
  Node1 ── P        ← 校验位跳到 Node1
  Node2 ── D3
  Node3 ── D4
  Node4 ── Q

Stripe3:
  Node1 ── Q        ← Q 跳到 Node1
  Node2 ── P        ← P 跳到 Node2
  Node3 ── D5
  Node4 ── D6

4.5 校验位轮转图示

复制代码
flowchart TD
    subgraph S1[Stripe 1]
        S1N1[Node1: D1]
        S1N2[Node2: D2]
        S1N3[Node3: P]
        S1N4[Node4: Q]
    end

    subgraph S2[Stripe 2]
        S2N1[Node1: P ← 跳到这里]
        S2N2[Node2: D3]
        S2N3[Node3: D4]
        S2N4[Node4: Q]
    end

    subgraph S3[Stripe 3]
        S3N1[Node1: Q ← 跳到这里]
        S3N2[Node2: P ← 跳到这里]
        S3N3[Node3: D5]
        S3N4[Node4: D6]
    end

    S1 --> S2 --> S3

    style S2N1 fill:#ffcccc
    style S3N1 fill:#ffcccc
    style S3N2 fill:#ffcccc

4.6 与传统 RAID 的关系

这种思想:

  • RAID5 的 Left Symmetric Layout(P 沿盘组轮转)
  • RAID6 的 Rotating Parity(P、Q 双校验位独立轮转)

在数学层面是一致的

实现层面完全不同

维度 传统 RAID5/6 S2D Parity
控制器 单个硬件 RAID 卡 软件层(Storage Spaces),跨服务器
故障域 单台服务器内的盘 Disk / Node / Enclosure / Rack
轮转粒度 盘级 Stripe + Fault Domain 联合优化
修复来源 仅同盘组 整个 Pool 的所有盘

也就是说,S2D 是把"RAID5/RAID6 的对称校验思想"搬到软件层 + 跨节点 + 多故障域

4.7 为什么"4 Node 才能用 Parity"

微软官方对 S2D 的支持矩阵:

  • 3 Node:只能 3-way Mirror
  • 4 Node 及以上:可以 Mirror,也可以 Dual Parity

这一点本文不展开技术根因(会单独写第三篇),但逻辑上的解释是

Dual Parity 需要 2 个独立校验位,最小 Stripe = 2 Data + 2 Parity = 4 列;再叠加 Fault Domain 约束,4 Node 是 S2D 默认推荐的最小 Parity 拓扑。


第 5 层:Fault Domain------为什么多节点越多越复杂

5.1 故障域的种类

Storage Spaces 在做 Column 选择时,会同时考虑多个故障域层级:

故障域 是否存在 典型场景
Disk 始终存在 任何硬盘
Node S2D / Azure Local 节点宕机 / 重启 / 维护
Enclosure 部分硬件 JBOD、Chassis
Rack Azure Local RAC 2510+ 机架级故障(电源、网络)

5.2 RAC 引入 Rack Fault Domain

到了 Azure Local 2510 的 Rack Aware Cluster,微软又增加了一层约束:

复制代码
Rack A
  ├─ Node1
  └─ Node2
Rack B
  ├─ Node3
  └─ Node4

Column 选择从以前的"避开同 Node",扩展为"避开同 Node + 同 Rack"。

每个 Stripe 的 Column 分配会同时考虑:

  • Disk Failure(磁盘故障)
  • Node Failure(节点宕机)
  • Chassis Failure(机箱故障)
  • Rack Failure(机架故障)

Column 的选择已经从单纯的"磁盘条带"演进为面向多级故障域的智能布局------这是 Azure Local 相比传统 RAID 控制器最大的架构差异。

5.3 RAC 故障域示意图

复制代码
flowchart TB
    subgraph RackA[Rack A]
        N1[Node1<br/>Disk 1-4]
        N2[Node2<br/>Disk 1-4]
    end

    subgraph RackB[Rack B]
        N3[Node3<br/>Disk 1-4]
        N4[Node4<br/>Disk 1-4]
    end

    subgraph RackC[Rack C]
        N5[Node5<br/>Disk 1-4]
        N6[Node6<br/>Disk 1-4]
    end

    Stripe[Stripe<br/>Columns=6] --> N1
    Stripe --> N3
    Stripe --> N5
    Stripe --> N2
    Stripe --> N4
    Stripe --> N6

    style RackA fill:#ffe4e1
    style RackB fill:#e0f7fa
    style RackC fill:#f1f8e9
    style Stripe fill:#fff3cd

5.4 一个容易踩的坑:Fault Domain 配错会直接锁死 Column

例如一个 4 Node 集群,如果:

  • 物理上 Node1 和 Node2 在同一 Rack
  • 但 RAC 配置没有声明 Rack Fault Domain

那么 Column 选择只会避开 Node,而不会避开 Rack------一旦这个 Rack 整体掉电(交换机 / PDU 故障),数据可能因"两个副本恰好在同一 Rack"而不可恢复。

因此 Azure Local RAC 部署时,正确声明 Fault Domain 拓扑是数据可靠性的硬性前提,不是"建议"。


6. Slab 大小估算(实战)

6.1 为什么估算 Slab 大小

Slab 大小直接决定:

  • Metadata 表的条目数(影响内存与启动速度)
  • Repair 粒度(影响重建性能)
  • 单次 IO 跨多少块盘

知道 Slab 大小有助于调优 Pool。

6.2 微软公开的 Slab 规则

微软在公开文档中给出过一个经验公式(属于"基于已公开文档的推断",不是精确数学公式):

复制代码
Slab 大小 ≈ Pool 总容量 / 物理盘数 / 某个常数

实际经验:

  • 在小规模 Pool(< 100 TB)中,Slab 通常在 256 MiB ~ 1 GiB 范围
  • 在大规模 Pool(> 500 TB)中,Slab 会被进一步切分

6.3 影响 Slab 大小的因素

因素 影响
物理盘数 越多 → Slab 越小(Metadata 越多)
单盘容量 越大 → Slab 越大
启用 Nested Resiliency(双层 Resiliency) Slab 重新划分
Performance History 算法根据访问模式动态调整

6.4 通过 PowerShell 查看 Slab 信息

复制代码
# 查看 Pool 的 Slab 配置(部分字段需要 Windows Server 2022+)
Get-StoragePool -FriendlyName S2DPool | Format-List *

# 查看 Virtual Disk 的 Column 数
Get-VirtualDisk | Select-Object FriendlyName, NumberOfColumns, ResiliencySettingName

# 查看每个盘的 Physical Extent 分布
Get-PhysicalDisk | Get-Disk | Get-Partition | Get-Volume

说明 :微软未公开完整的 Slab 大小计算公式;上面只是经验范围,不写"权威数字"。


7. Repair 路径详解

7.1 Repair 是什么

当一块盘或一个 Node 出现故障后,Storage Spaces 会从 Pool 内其他健康的盘 读取副本/校验数据,重新生成丢失的副本,并写入新的位置------这个过程叫 Repair

7.2 Repair 的两种场景

场景 触发条件 重建来源
Disk Repair 单盘坏 同 Pool 内其他盘
Node Repair 整机宕机 同 Pool 内其他 Node

7.3 Repair 的流程

复制代码
sequenceDiagram
    participant Disk as 故障盘
    participant Meta as Pool Metadata
    participant Src as 源盘(健康副本)
    participant Dst as 目标盘(写入新副本)
    participant App as 前端应用

    Disk->>Meta: 检测到故障 / 标记 offline
    Meta->>Meta: 列出该盘上所有 Slab
    loop 每个 Slab
        Meta->>Src: 读取现有副本(Mirror 或 Parity 重算)
        Src-->>Meta: 返回数据
        Meta->>Dst: 写入新副本
        Dst-->>Meta: ACK
        Meta->>Meta: 更新 Metadata
    end
    Meta->>App: 通知 Repair 完成

7.4 Repair 的关键认知

(1) Repair 的源数据可以是任意健康盘

不像传统 RAID 只能从同盘组修复,S2D 可以从整个 Pool 任意盘读源数据。这就是"Every Drive is a Spare"。

(2) Repair 是后台任务

Repair 默认是后台异步执行,不阻塞前端 IO;但会消耗 IO 带宽,可能影响业务性能。

(3) 多个盘同时故障的 Repair 顺序

微软按"重要数据先修"的策略,但具体排序算法未公开。

(4) Azure Local 上的 Repair 加速

Windows Server 2022+ 起,Azure Local 上的 Repair 速度比传统 S2D 更快;微软未公开具体加速比,不写绝对数字


8. 与 Ceph / vSAN / 传统 RAID 的对比

维度 传统硬件 RAID S2D / Azure Local Ceph VMware vSAN
数据单位 LBA / Stripe Slab + 64 MiB Region Object + PG Component
副本位置 固定盘对 Column 轮转 CRUSH 哈希 RAID-1/5/6 策略
校验位 固定盘 P、Q Stripe 轮转 Jerasure / ISA-L RAID-5/6 擦除
故障域 单台服务器 Disk / Node / Enclosure / Rack Host / Rack / Room Host / Rack
Metadata RAID 卡 NVRAM Pool Metadata 全局 OSD Map + PG Log DOM + Witness
修复来源 同盘组热备 整个 Pool 整个 Cluster 整个 Cluster
横向扩展 不支持 原生 2~16 Node 无理论上限 推荐 2~64 Host
Placement 算法 静态 动态 Fault Domain CRUSH 哈希 策略 + Witness

结论:S2D 在数据布局算法上接近 Ceph / vSAN(都是元数据驱动的分布式放置),但缺少 Placement Group 这类"局部修复"机制,全局 Metadata 是它的最大瓶颈。


9. 总结

复制代码
flowchart TD
    A[VM 写入请求] --> B[ReFS<br/>64 MiB Region 划分]
    B --> C[Storage Spaces Metadata]
    C --> D[Slab Mapping<br/>查找物理位置]
    D --> E[Column Selection<br/>Fault Domain Aware]
    E --> F1[Mirror: 三副本轮转]
    E --> F2[Parity: P Q 轮转]
    E --> F3[MAP: 先 Mirror 后 Rotate]
    F1 --> G[Physical Disk]
    F2 --> G
    F3 --> G

    style A fill:#e1f5ff
    style B fill:#fff3cd
    style E fill:#d4edda
    style G fill:#f8d7da

9.1 与传统 RAID 的本质差异

维度 传统 RAID Storage Spaces / S2D
数据单位 LBA / Stripe Slab + 64 MiB Region
副本位置 固定盘对 动态 Column 轮转
校验位 固定盘 P、Q 都参与 Stripe 级轮转
故障域 单台服务器 Disk / Node / Enclosure / Rack
Metadata RAID 卡 NVRAM 全局 Pool Metadata
修复来源 同盘组热备 整个 Pool 任意盘
横向扩展 不支持 原生支持 2~16 Node

9.2 与分布式 SDS 的相似性

正因为这套机制------Slab、Column 轮转、Fault Domain Aware、Pool 级 Metadata------S2D 才被普遍认为更接近 Ceph(CRUSH + PG)VMware vSAN(RAID-5/6 擦除编码)Google Colossus,而不是传统 RAID:

数据布局是由元数据驱动的分布式放置算法决定的,而不是固定 RAID 条带或固定磁盘顺序。

这也是为什么本文把它叫作"五层数据布局原理"------微软文档不写 Slab、不写 Column 轮转算法、不写 Fault Domain 权重,是因为这些实现细节在官方层面属于"内部行为",但它们恰恰是理解 S2D 性能、容量、修复行为的关键


10. 后续阅读

  • 第二篇:Mirror / Parity / MAP 数据流与 Region Rotation 的细节
  • 第三篇:为什么 3 节点必须 3-way Mirror、4 节点才能用 Parity、为什么单校验被淘汰
  • 第四篇:Windows Server 2025 在 Storage Spaces 上做了什么、没做什么,以及下一代方向预测
相关推荐
鱼忆梦2 小时前
初识 Hermes 及工作中的简单应用
microsoft
XUHUOJUN18 小时前
Azure Local GPU 部署与企业应用场景指南(下篇)
microsoft·架构·nvidia·azure local
星释21 小时前
鸿蒙智能体开发实战:26.Skill 与插件协同开发
microsoft·华为·ai·harmonyos·鸿蒙·智能体
XUHUOJUN1 天前
Azure Local GPU 部署与企业应用场景指南(上篇 )
windows·microsoft·架构·nvidia·azure local
L歪歪君1 天前
开源大模型本地部署硬件选型深度指南
microsoft
步步为营DotNet1 天前
借助 Microsoft.Extensions.AI 与 ASP.NET Core 10 实现智能 Web 应用故障预测
人工智能·microsoft·asp.net
YHHLAI2 天前
Agent 智能体开发实战 · 第四课:完整工具集 —— 打造 AI 编程 Agent 的工具箱
人工智能·windows·microsoft
Tom·Ge2 天前
【Spring Boot 4】Spring Boot 4.0 AI Agent实战:Skills Agent + MCP协议集成
人工智能·spring boot·microsoft
time展天12 天前
.NET 10 使用 Microsoft.AspNetCore.OpenApi 实现 API 版本管理
microsoft·.net