MinIO源码深度解读-第2章第1节-纠删码原理与源码总览-erasure.go-完整架构分析

发布时间:2026/11/4 2:12:59
MinIO源码深度解读-第2章第1节-纠删码原理与源码总览-erasure.go-完整架构分析

MinIO源码深度解读 - 第4章第1节:纠删码原理与源码总览——erasure.go 完整架构分析

erasure.go 是 MinIO 的核心文件,实现了纠删码的完整工作流程。这篇文章带你全面理解纠删码在 MinIO 中的实现。

本文导航

  • 纠删码概述
  • erasure.go 架构
  • 编码实现
  • 解码实现
  • 性能优化

纠删码概述

MinIO 使用的纠删码算法是Reed-Solomon,一种 Maximum Distance Separable (MDS) 码。

Reed-Solomon 原理

输入:K 个数据块 输出:N = K + M 个块(K 数据 + M 校验) 容错:任意 M 个块丢失可恢复

MinIO 的纠删码配置

配置数据块 (K)校验块 (M)容错数存储效率
4 盘22250%
8 盘44450%
12 盘66650%
16 盘88850%

erasure.go 架构

核心结构

// cmd/erasure.go// Erasure ObjectstypeErasureObjectsstruct{// Set 管理sets*ErasureSets// 纠删码参数kint// 数据块数mint// 校验块数// 编码/解码器encoder*reedsolomon.Encoder decoder*reedsolomon.Decoder// 块大小blockSizeint64// 分片大小shardSizeint64// 并发控制mu sync.RWMutex// 缓存cache*sync.Map}// Erasure Set 管理typeErasureSetsstruct{// Set 数组sets[]*ErasureSet// Set 数量setCountint// 每个 Set 的磁盘数setDriveCountint}// Erasure SettypeErasureSetstruct{// Set 索引indexint// 磁盘disks[]StorageAPI// 锁lock sync.RWMutex// 状态state SetState}

模块关系

ErasureObjects (纠删码对象层) ├── ErasureSets (集管理) │ └── ErasureSet (单个集) │ └── StorageAPI (存储接口) ├── Encoder (编码器) ├── Decoder (解码器) └── Cache (缓存)

编码实现

数据分片

// cmd/erasure.go// 将数据分片func(eo*ErasureObjects)splitData(data[]byte)([][]byte,error){// 1. 计算分片数numShards:=(len(data)+eo.blockSize-1)/int(eo.blockSize)// 2. 创建分片数组shards:=make([][]byte,numShards)// 3. 分片fori:=0;i<numShards;i++{start:=i*int(eo.blockSize)end:=start+int(eo.blockSize)ifend>len(data){end=len(data)}shards[i]=data[start:end]}returnshards,nil}// Reed-Solomon 编码func(eo*ErasureObjects)encode(data[]byte)([][]byte,error){// 1. 分片shards,err:=eo.splitData(data)iferr!=nil{returnnil,err}// 2. 编码encoded,err:=eo.encoder.Encode(shards)iferr!=nil{returnnil,err}returnencoded,nil}

编码与写入

// cmd/erasure.go// 编码并写入所有分片func(eo*ErasureObjects)encodeAndWrite(ctx context.Context,bucket,objectstring,data[]byte)error{// 1. 编码shards,err:=eo.encode(data)iferr!=nil{returnerr}// 2. 获取 Erasure Setset,setIndex:=eo.sets.getByIndex(bucket,object)ifset==nil{returnfmt.Errorf("erasure set not found")}// 3. 写入每个分片fori,shard:=rangeshards{disk:=set.disks[i%len(set.disks)]shardPath:=pathJoin(bucket,object,fmt.Sprintf("shard-%d",i))iferr:=disk.WriteFile(ctx,shardPath,shard);err!=nil{// 写入失败,清理已写入的分片eo.cleanupShards(ctx,bucket,object)returnerr}}returnnil}// 清理分片func(eo*ErasureObjects)cleanupShards(ctx context.Context,bucket,objectstring){set,_:=eo.sets.getByIndex(bucket,object)ifset==nil{return}for_,disk:=rangeset.disks{shardPath:=pathJoin(bucket,object,"shard-*")disk.DeleteFile(ctx,shardPath)}}

解码实现

分片读取

// cmd/erasure.go// 读取分片func(eo*ErasureObjects)readShards(ctx context.Context,bucket,objectstring)([][]byte,error){// 1. 获取 Erasure Setset,setIndex:=eo.sets.getByIndex(bucket,object)ifset==nil{returnnil,fmt.Errorf("erasure set not found")}// 2. 读取所有分片shards:=make([][]byte,len(set.disks))missingShards:=make([]int,0)fori,disk:=rangeset.disks{shardPath:=pathJoin(bucket,object,fmt.Sprintf("shard-%d",i))data,err:=disk.ReadFile(ctx,shardPath,0,-1)iferr!=nil{missingShards=append(missingShards,i)shards[i]=nilcontinue}shards[i]=data}// 3. 检查是否有足够的分片iflen(shards)-len(missingShards)<eo.k{returnnil,fmt.Errorf("not enough shards to decode: %d/%d",len(shards)-len(missingShards),eo.k)}returnshards,nil}// Reed-Solomon 解码func(eo*ErasureObjects)decode(shards[][]byte)([]byte,error){// 1. 创建分片矩阵matrix:=make([][]byte,eo.setDriveCount)fori:=0;i<eo.setDriveCount;i++{ifshards[i]!=nil{matrix[i]=shards[i]}else{// 缺失的分片用零填充matrix[i]=make([]byte,eo.blockSize)}}// 2. 解码decoded,err:=eo.decoder.Decode(matrix)iferr!=nil{returnnil,err}// 3. 拼接分片result:=make([]byte,0)for_,shard:=rangedecoded[:eo.k]{result=append(result,shard...)}returnresult,nil}

性能优化

并发编码

// cmd/erasure.go// 并发编码func(eo*ErasureObjects)encodeConcurrent(data[]byte)([][]byte,error){// 1. 分片shards,err:=eo.splitData(data)iferr!=nil{returnnil,err}// 2. 并发编码encoded:=make([][]byte,len(shards))varwg sync.WaitGroupfori,shard:=rangeshards{wg.Add(1)gofunc(idxint,data[]byte){deferwg.Done()encoded[idx],_=eo.encoder.EncodeSingle(data)}(i,shard)}wg.Wait()returnencoded,nil}// 并发解码func(eo*ErasureObjects)decodeConcurrent(shards[][]byte)([]byte,error){// 1. 创建分片矩阵matrix:=make([][]byte,eo.setDriveCount)fori:=0;i<eo.setDriveCount;i++{matrix[i]=shards[i]}// 2. 解码decoded,err:=eo.decoder.Decode(matrix)iferr!=nil{returnnil,err}// 3. 拼接result:=make([]byte,0)for_,shard:=rangedecoded[:eo.k]{result=append(result,shard...)}returnresult,nil}

缓存优化

// cmd/erasure.go// 编码缓存typeEncodingCachestruct{// 缓存数据datamap[string][]byte// 缓存大小sizeint64// 缓存上限maxSizeint64// 锁mu sync.RWMutex}// 获取缓存func(c*EncodingCache)Get(keystring)([]byte,bool){c.mu.RLock()deferc.mu.RUnlock()ifdata,ok:=c.data[key];ok{returndata,true}returnnil,false}// 设置缓存func(c*EncodingCache)Set(keystring,data[]byte){c.mu.Lock()deferc.mu.Unlock()ifc.size+int64(len(data))>c.maxSize{// 缓存已满,驱逐c.evict()}c.data[key]=data c.size+=int64(len(data))}// 驱逐缓存func(c*EncodingCache)evict(){// LRU 驱逐// ...}

小结

本文分析了 MinIO 的纠删码架构:

  1. 核心结构:ErasureObjects、ErasureSets、ErasureSet
  2. 编码实现:数据分片 → Reed-Solomon 编码 → 写入磁盘
  3. 解码实现:读取分片 → Reed-Solomon 解码 → 拼接数据
  4. 性能优化:并发编码、缓存优化

纠删码是 MinIO 数据冗余的核心——没有它就没有分布式存储。

下节预告

下节预告:纠删码对象操作——erasure-object.go 中 PUT/GET/DELETE 的深度源码分析。


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