golang文件存储纠删码实现

简介: // Verify(shards [][]byte) (bool, error)。每个分片都是[]byte类型,分片集合就是[][]byte类型,传入所有分片,如果有任意的分片数据错误,就返回false。

一般我们存储简单处理就是写三副本,但是三副本的成本太大了,采用纠删码可以比较好的降低存储空间的成本,具体看下golang中的代码实现。

// 纠删码测试
// 将一个文件拆分成10分,删除其中的任意三分,尝试还原文件
// 这边需要注意的一点是文件的拆分后的顺序和还原的顺序是相关的,顺序错误是无法还原的

// 其中Encoder接口有以下几个关键的函数。
// Verify(shards [][]byte) (bool, error)。每个分片都是[]byte类型,分片集合就是[][]byte类型,传入所有分片,如果有任意的分片数据错误,就返回false。
// Split(data []byte) ([][]byte, error)。将原始数据按照规定的分片数进行切分。注意:数据没有经过拷贝,所以修改分片也就是修改原数据。
// Reconstruct(shards [][]byte) error。  这个函数会根据shards中完整的分片,重建其他损坏的分片。
// Join(dst io.Writer, shards [][]byte, outSize int) error。将shards合并成完整的原始数据并写入dst这个Writer中。


package main

import (
    "flag"
    "fmt"
    "io/ioutil"
    "os"
    "strings"

    "github.com/klauspost/reedsolomon"
    "github.com/qtj/gosdk/file"
)

定义和初始化验证一些参数

var (
    srcFile      string // 原始文件
    dstDir       string // 目标目录
    recoverName  string // 还原后的文件名称
    dataShards   int    // 数据分片
    parityShards int    // 校验分片
    oper         string // 操作动作
)

func init() {
    flag.StringVar(&srcFile, "srcFile", "qtjErasureCode.exe", "原始文件名称")
    flag.StringVar(&dstDir, "dstDir", "dstDir", "目标目录")
    flag.StringVar(&recoverName, "recoverName", "recoverName", "还原后的文件名称")
    flag.StringVar(&oper, "oper", "", "split和recover二选一,split会将一个文件拆分成类似10个数据文件和3和校验文件,recover的时候可以删除目标目录下的三个文件做还原即可")
    flag.IntVar(&dataShards, "dataShards", 10, "数据分片个数")
    flag.IntVar(&parityShards, "parityShards", 3, "校验分片个数")
    flag.Parse()
}

主调用

// qtjErasureCode.exe -oper=split先将文件拆分
// 删除当前目录下的子文件夹dstDir里面的任意三个文件
// qtjErasureCode.exe -oper=recover -recoverName="recover.exe" 将文件还原
// 最后比对md5发现是一致的
func main() {
    if !strings.Contains(dstDir, "/") && !strings.Contains(dstDir, "\\") {
        dstDir = file.GetCurDir() + dstDir + "/"
    }
    if oper == "split" {
        file.RemoveDirTree(dstDir)
        file.CreateDirTree(dstDir)
        if err := splitFile(); err != nil {
            fmt.Println(err)
            return
        }
    } else if oper == "recover" {
        if err := recoverFile(); err != nil {
            fmt.Println(err)
            return
        }
    } else {
        fmt.Println("错误的操作动作参数,oper必须为split或者recover")
        return
    }
}

还原文件验证

func recoverFile() error {
    if recoverName == "" {
        return fmt.Errorf("还原后的文件名称%s不能为空", recoverName)
    }

    // 数据分10片和校验3片
    enc, err := reedsolomon.New(dataShards, parityShards)
    if err != nil {
        return fmt.Errorf("创建数据分片和校验分片失败,%s", err.Error())
    }

    shards := make([][]byte, dataShards+parityShards)
    for i := range shards {
        splitName := fmt.Sprintf("%ssplit%010d", dstDir, i)
        // 不管文件是否存在,需要保留原先的顺序
        if shards[i], err = ioutil.ReadFile(splitName); err != nil {
            fmt.Printf("读取文件[%s]失败,%s\n", splitName, err.Error())
        }
        fmt.Println(splitName)
    }

    ok, err := enc.Verify(shards)
    if ok {
        fmt.Println("非常好,数据块和校验块都完整")
    } else {
        if err = enc.Reconstruct(shards); err != nil {
            return fmt.Errorf("重建其他损坏的分片失败,%s", err.Error())
        }

        if ok, err = enc.Verify(shards); err != nil {
            return fmt.Errorf("数据块校验失败2,%s", err.Error())
        }
        if !ok {
            return fmt.Errorf("重建其他损坏的分片后数据还是不完整,文件损坏")
        }

    }
    f, err := os.Create(recoverName)
    if err != nil {
        return fmt.Errorf("创建还原文件[%s]失败,%s", recoverName, err.Error())
    }
    // 这部分的大小决定了还原后的大小和原先的是不是一致的,不然使用md5比对或者大小都是不一样的
    // 实际生产需要一开始就拆分文件时候就记录总的大小
    //if err = enc.Join(f, shards, len(shards[0])*dataShards); err != nil {
    _, ln, err := file.GetFileLenAndMd5(srcFile)
    if err != nil {
        return fmt.Errorf("计算原始文件[%s]大小失败,%s", srcFile, err.Error())
    }
    if err = enc.Join(f, shards, int(ln)); err != nil {
        return fmt.Errorf("写还原文件[%s]失败,%s", recoverFile(), err.Error())
    }
    return nil
}

分隔文件处理

func splitFile() error {
    // 数据分10片和校验3片
    enc, err := reedsolomon.New(dataShards, parityShards)
    if err != nil {
        return fmt.Errorf("创建数据分片和校验分片失败,%s", err.Error())
    }

    bigfile, err := ioutil.ReadFile(srcFile)
    if err != nil {
        return fmt.Errorf("读取原始文件[%s]失败,%s", srcFile, err.Error())
    }

    // 将原始数据按照规定的分片数进行切分
    shards, err := enc.Split(bigfile)
    if err != nil {
        return fmt.Errorf("针对原始文件[%s]拆分成数据[%d]块,校验[%d]块失败,%s", srcFile, dataShards, parityShards, err.Error())
    }

    // 编码校验块
    if err = enc.Encode(shards); err != nil {
        return fmt.Errorf("编码校验块失败,%s", err.Error())
    }
    for i := range shards {
        splitName := fmt.Sprintf("%ssplit%010d", dstDir, i)
        fmt.Println(splitName)
        if err = file.SaveFile(shards[i], splitName); err != nil {
            return fmt.Errorf("原始文件[%s]拆分文件[%s]写失败,%s", srcFile, splitName, err.Error())
        }
    }

    return nil
}
目录
相关文章
golang操作文件
1、读取文件信息: /* 读取文件信息 */ func readFile(path string) string { fi, err := os.Open(path) if err != nil { panic(err) } defer fi.
1404 0
|
4天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
33 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
24天前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
37 7
|
24天前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
24天前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
99 71
|
23天前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
103 67
|
26天前
|
Go 索引
go语言for遍历数组或切片
go语言for遍历数组或切片
94 62
|
24天前
|
存储 Go
go语言中映射
go语言中映射
36 11
|
26天前
|
Go
go语言for遍历映射(map)
go语言for遍历映射(map)
33 12