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 盘 | 2 | 2 | 2 | 50% |
| 8 盘 | 4 | 4 | 4 | 50% |
| 12 盘 | 6 | 6 | 6 | 50% |
| 16 盘 | 8 | 8 | 8 | 50% |
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 的纠删码架构:
- 核心结构:ErasureObjects、ErasureSets、ErasureSet
- 编码实现:数据分片 → Reed-Solomon 编码 → 写入磁盘
- 解码实现:读取分片 → Reed-Solomon 解码 → 拼接数据
- 性能优化:并发编码、缓存优化
纠删码是 MinIO 数据冗余的核心——没有它就没有分布式存储。
下节预告
下节预告:纠删码对象操作——erasure-object.go 中 PUT/GET/DELETE 的深度源码分析。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连!
本系列持续更新中,关注不迷路~