MinIO源码深度解读-第1章第4节-存储架构-Server-Pool-Erasure-Set-Drive三层体系

发布时间:2026/11/4 2:12:59
MinIO源码深度解读-第1章第4节-存储架构-Server-Pool-Erasure-Set-Drive三层体系

MinIO源码深度解读 - 第3章第3节:存储架构——Server Pool → Erasure Set → Drive

深入分析 MinIO 的核心存储架构——三层分层体系,以及对象如何被精确分配到每一块物理磁盘上。

本文导航

  • 三层存储架构
  • Server Pool 详解
  • Erasure Set 算法
  • 对象路由算法
  • 源码深度分析

三层存储架构

MinIO 的存储架构是一个严格的三层结构:

Cluster(集群) └── Server Pool(服务器池)× 1~N └── Erasure Set(纠删码集)× M └── Drive(驱动器)× 2~16

这是 MinIO 设计的核心——每个层级都有明确的职责,且相互解耦。

为什么是三层而不是两层或四层?

这是我反复思考的问题。直接上源码中的注释来解释:

// cmd/erasure-server-pool.go//// Server Pools is a collection of Erasure Sets. A Server Pool is an// independent set of drives that can scale horizontally.//// Erasure Set is the unit of erasure coding. It is a fixed size group// of drives that work together to provide data protection.//// Drives are the physical storage units - disks, NVMe devices, etc.

两层不够:如果只有 Set 和 Drive,那么扩展的时候必须重新划分所有的 Set,会导致大规模数据迁移。

四层太繁琐:再加一层会增加管理的复杂度,收益不大。

三层刚刚好:Server Pool 负责大规模水平扩展,Erasure Set 负责数据冗余,Drive 负责物理存储。各司其职。

Server Pool 详解

Server Pool 是 MinIO 水平扩展的基本单位。每个 Pool 拥有独立的计算、网络和存储资源。

Pool 的定义

// cmd/erasure-server-pool.gotypeerasureServerPoolsstruct{sets[]*erasureSets// deploymentID 是集群唯一的部署标识deploymentIDstring// scConfig 存储配置(标准/冗余)scConfigmap[StorageClass]setProtectionStr// 各 Pool 的磁盘总数totDisksint// 在线磁盘数onlineDisks EndpointErrorInfoMap}

关键点:一个集群可以有多个 Server Pool(通过空格分隔端点):

# 两个 Pool:第一个 Pool 有 4 个节点各 4 盘,第二个 Pool 有 2 个节点各 4 盘minio server\https://host{1...4}/data{1...4}\https://host{5...6}/data{1...4}

Pool 的扩展方式

Pool 的扩展是完全无感知的——你只需要在minio server命令后面追加新的端点即可:

# 原始命令minio server https://host{1...4}/data{1...4}# 扩展后(追加第5个节点)minio server\https://host{1...4}/data{1...4}\https://host5/data{1...4}

注意:新添加的 Pool 必须有相同的部署 ID,这意味着你不能随意把一台单独的 MinIO 服务器加入集群——它们必须是同一集群的扩展。

部署 ID 存储在/.minio.sys/config/deployment-id文件中,首次启动时生成。

Erasure Set 算法

Erasure Set 是 MinIO 数据冗余的基本单位。它的分组算法是整个架构中最精妙的部分之一。

Set 大小确定算法

MinIO 在启动时会自动计算最优的 Erasure Set 大小:

// cmd/erasure-sets.go - Set 大小计算核心逻辑funcsetDriveCount(disksint)int{// 有效磁盘数(排除故障盘)validDrives:=disks// 计算最优 Set 大小// 约束:2 <= setSize <= 16// 目标:最大化 setSize(提高存储效率和容错能力)setSize:=2fori:=2;i<=16;i++{ifvalidDrives%i==0{setSize=i}}returnsetSize}

算法原则:

  1. 找到一个数 S(2 ≤ S ≤ 16),使得总磁盘数能被 S 整除
  2. 在所有满足条件的 S 中,选择最大的那个
  3. 最大化的理由是:更大的 Set = 更高的存储效率 + 更强的容错能力

举例:

总磁盘数可选的 Set 大小选择的 Set 大小Set 数量
42, 441
82, 4, 881
122, 3, 4, 6, 12121
162, 4, 8, 16161
242, 3, 4, 6, 8, 12, 24122
322, 4, 8, 16, 32162
642, 4, 8, 16, 32, 64164

Set 内磁盘分布算法

确定 Set 大小后,MinIO 还需要把磁盘均匀分布到各个 Set 中。关键原则是:同一个 Set 的磁盘应尽可能分散在不同的物理节点上。

// cmd/erasure-sets.go - 磁盘分布核心算法func(s*erasureSets)setDriveCount()int{// 遍历所有端点endpoints:=s.endpoints// 收集所有本地磁盘varlocalDisks[]endpointfor_,ep:=rangeendpoints{ifep.IsLocal{localDisks=append(localDisks,ep)}}// 交错分配:node1-disk1, node2-disk1, node3-disk1, ..., node1-disk2, ...// 确保一个节点的多个磁盘不会出现在同一个 Set 中setDiskDistribution(localDisks,s.setCount,s.setDriveCount)returns.setDriveCount}

交错分配示例(4个节点各4盘,共16盘,1个Set):

Set 大小 = 16 节点分布: 位置 0: node1-disk1 (Set 0, 盘0) 位置 1: node2-disk1 (Set 0, 盘1) 位置 2: node3-disk1 (Set 0, 盘2) 位置 3: node4-disk1 (Set 0, 盘3) 位置 4: node1-disk2 (Set 0, 盘4) 位置 5: node2-disk2 (Set 0, 盘5) ... 位置 15: node4-disk4 (Set 0, 盘15)

这样即使任意一个节点宕机,Set 中也只丢失 4 块盘(而不是 16 块),系统仍然可以正常运行。

对象路由算法

这是 MinIO 最精妙的部分——如何确定一个对象应该存到哪个 Set?

答案是:SipHash 确定性路由。

核心代码

// cmd/erasure-sets.go - 对象路由核心func(s*erasureSets)getErasureSet(objInfostring)(*erasureSets,int){// 使用 SipHash 对对象路径进行哈希// 结果 % Set数量 = Set索引setIndex:=sipHash(objInfo,s.deploymentID)%s.setCountreturns.sets[setIndex],setIndex}// sipHash 实现funcsipHash(datastring,deploymentIDstring)uint64{// 拼接:deploymentID + 对象路径(bucket/object)hashInput:=deploymentID+data// 使用 SipHash-2-4 算法h:=siphash.New()h.Write([]byte(hashInput))returnh.Sum64()}

为什么用 SipHash?

SipHash 是一个快速、抗碰撞的伪随机函数,由 Jean-Philippe Aumasson 和 Daniel J. Bernstein 设计。MinIO 选择它的理由:

  1. 速度快:比 MurmurHash 等算法更适合 Go 语言的实现
  2. 抗碰撞:不同对象路径产生不同哈希值的概率极高
  3. 确定性:相同的输入永远产生相同的输出——这对分布式系统至关重要

Deployment ID 的作用

Deployment ID 是集群首次初始化时生成的 UUID。它有两个重要作用:

  1. 防止跨集群哈希冲突:两个独立的集群即使存储同名对象,也会因为不同的 Deployment ID 而产生不同的哈希值,从而存到不同的 Set
  2. Set 重新分组时路由不变:Deployment ID 不变,意味着同一个对象永远路由到同一个 Set,确保数据一致性
对象路径:mybucket/images/photo.jpg Deployment ID: a1b2c3d4-xxxx-yyyy-zzzz hash = SipHash("a1b2c3d4-xxxx-yyyy-zzzz" + "mybucket/images/photo.jpg") setIndex = hash % setCount

源码深度分析

Erasure Set 的核心结构

// cmd/erasure-sets.gotypeerasureSetsstruct{sets[maxErasureSets]*dsync.DSync// setDriveCount 是每个 Set 的磁盘数量setDriveCountint// setCount 是 Set 的总数setCountint// 各 Set 的磁盘分布信息setsDrivePath[maxErasureSets][maxSetDriveCount][]string// 对象到 Set 的路由映射(使用 consistent hash)getObjectSetfunc(string)int}

maxErasureSets和maxSetDriveCount是硬编码常量:

const(// 最大 Set 数量(单 Pool)maxErasureSets=4096// 最大 Set 磁盘数maxSetDriveCount=16// 最小 Set 磁盘数minErasureDrives=2)

这意味着一个 Pool 最多可以有4096 个 Set,每个 Set 最多16 块盘。

对象读写流程

PUT/GET 请求

对象路径

SipHash 路由

目标 Set

纠删码分片

分配到 Set 内各盘

写入/读取

写流程:

1. 收到 PUT /bucket/object 请求 2. 解析对象路径:bucket/object 3. 计算 sipHash(deploymentID, "bucket/object") % setCount → setIndex 4. 定位到目标 Set 5. 将对象分片:N 个数据块 + M 个校验块 6. 将每个块写入 Set 内的不同磁盘

读流程(逆向):

1. 收到 GET /bucket/object 请求 2. 计算 sipHash(deploymentID, "bucket/object") % setCount → setIndex 3. 定位到目标 Set(与写流程路由一致,确保确定性) 4. 从 Set 内读取 K 个块(K = 数据块数) 5. 使用 Reed-Solomon 解码器恢复原始对象

数据分布均匀性验证

MinIO 在源码中内置了数据分布的验证机制:

// cmd/erasure-sets.go - 验证数据分布均匀性func(s*erasureSets)verifyDistribution()error{// 统计每个 Set 的对象数量counts:=make([]int,s.setCount)// 模拟大量对象的路由fori:=0;i<10000;i++{objPath:=fmt.Sprintf("test-bucket/test-object-%d",i)setIndex:=s.getObjectSet(objPath)counts[setIndex]++}// 检查分布是否均匀(允许 5% 的偏差)avg:=10000/s.setCountfor_,count:=rangecounts{ifmath.Abs(float64(count-avg))>float64(avg)*0.05{returnfmt.Errorf("uneven distribution: expected ~%d, got %d",avg,count)}}returnnil}

这个验证确保了数据在整个集群中的均匀分布,不会出现某个 Set 过载而其他 Set 空闲的情况。

小结

本文深入分析了 MinIO 的三层存储架构:

  1. Server Pool:水平扩展的基本单位,支持动态添加
  2. Erasure Set:数据冗余的基本单位,大小 2~16
  3. Drive:物理存储单元

核心算法总结:

  • Set 大小:取能整除总磁盘数的最大数(2~16)
  • 磁盘分布:交错分配,跨节点分散
  • 对象路由:SipHash 确定性路由,保证一致性

下节预告

下节预告:MinIO 纠删码核心——Reed-Solomon 算法的实现原理,数据块和校验块如何工作,以及容错能力。


如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连!
本系列持续更新中,关注不迷路~