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.
1395 0
|
1天前
|
程序员 Go PHP
为什么大部分的 PHP 程序员转不了 Go 语言?
【9月更文挑战第8天】大部分 PHP 程序员难以转向 Go 语言,主要因为:一、编程习惯与思维方式差异,如语法风格和编程范式;二、学习成本高,需掌握新知识体系且面临项目压力;三、职业发展考量,现有技能价值及市场需求不确定性。学习新语言虽有挑战,但对拓宽职业道路至关重要。
22 10
|
2天前
|
算法 程序员 Go
PHP 程序员学会了 Go 语言就能唬住面试官吗?
【9月更文挑战第8天】学会Go语言可提升PHP程序员的面试印象,但不足以 solely “唬住” 面试官。学习新语言能展现学习能力、拓宽技术视野,并增加就业机会。然而,实际项目经验、深入理解语言特性和综合能力更为关键。全面展示这些方面才能真正提升面试成功率。
20 10
|
1天前
|
编译器 Go
go语言学习记录(关于一些奇怪的疑问)有别于其他编程语言
本文探讨了Go语言中的常量概念,特别是特殊常量iota的使用方法及其自动递增特性。同时,文中还提到了在声明常量时,后续常量可沿用前一个值的特点,以及在遍历map时可能遇到的非顺序打印问题。
|
6天前
|
安全 大数据 Go
深入探索Go语言并发编程:Goroutines与Channels的实战应用
在当今高性能、高并发的应用需求下,Go语言以其独特的并发模型——Goroutines和Channels,成为了众多开发者眼中的璀璨明星。本文不仅阐述了Goroutines作为轻量级线程的优势,还深入剖析了Channels作为Goroutines间通信的桥梁,如何优雅地解决并发编程中的复杂问题。通过实战案例,我们将展示如何利用这些特性构建高效、可扩展的并发系统,同时探讨并发编程中常见的陷阱与最佳实践,为读者打开Go语言并发编程的广阔视野。
|
3天前
|
存储 Shell Go
Go语言结构体和元组全面解析
Go语言结构体和元组全面解析
|
8天前
|
Go
golang语言之go常用命令
这篇文章列出了常用的Go语言命令,如`go run`、`go install`、`go build`、`go help`、`go get`、`go mod`、`go test`、`go tool`、`go vet`、`go fmt`、`go doc`、`go version`和`go env`,以及它们的基本用法和功能。
20 6
|
8天前
|
存储 Go
Golang语言基于go module方式管理包(package)
这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。
16 3
|
10天前
|
缓存 安全 Java
如何利用Go语言提升微服务架构的性能
在当今的软件开发中,微服务架构逐渐成为主流选择,它通过将应用程序拆分为多个小服务来提升灵活性和可维护性。然而,如何确保这些微服务高效且稳定地运行是一个关键问题。Go语言,以其高效的并发处理能力和简洁的语法,成为解决这一问题的理想工具。本文将探讨如何通过Go语言优化微服务架构的性能,包括高效的并发编程、内存管理技巧以及如何利用Go生态系统中的工具来提升服务的响应速度和资源利用率。