golang分布式存储 读书笔记(3)——数据冗余之RS码

对象存储的数据冗余

如果数据只存储一份,存储设备坏了数据就丢失了,所以需要做数据冗余。

常见的数据冗余策略就是多副本冗余,该策略实现简单,但是代价比较高。书中介绍的冗余策略是使用Reed-Solomon纠删码实现的。

RS纠删码中有数据片校验片的概念,假如说选择4个数据片,那么就会将数据分成4个分片对象。每个分片的大小是原始对象的25%,如果选择2个校验片,那么会同时生成2个和数据片大小一样的校验片,所以一个文件最后会得到6个分片。

神奇的是,6个分片里面,只要有任意4个分片没有损坏,都可以还原出原始文件。

评价一个数据冗余策略的好坏,主要是衡量该策略对存储空间的要求和其抗数据损坏的能力。

  • 对存储空间的要求是指我们采用的冗余策略相比于不使用冗余要额外支付的存储空间,用百分比表示。
  • 抗数据损坏的能力以允许损坏或丢失的对象数量来衡量。

例如,在不使用冗余策略的情况下,我们的对象占用存储空间的大小就是它本身的大小,一旦该对象损坏,我们就丢失了这个对象,所以它对存储空间的要求使100%,抵御能力是0;如果使用双副本冗余策略,存储空间要求是200%,抵御数据损坏的能力是1(可以丢失两个副本中的任意1个);而使用4+2RS码的策略,存储空间的要求是150%,抵御能力是2(可以丢失6个分片对象中的任意2个);而对于一个M+NRS码(M个数据片加N个校验片),其对存储空间的要求是(M+N)/M*100%,抵御能力是N

RS码原理简介

原理比较复杂,这里抄一个网上博客的例子:假设选择4个数据片,2个校验片,有一个文件需要备份,文件内容是ABCDEFGHIJKLMNOP。首先将其分成4个分片,得到一个二维数组,每一行是一个数据分片,共4行。

The Original Data.png

RS算法会使用一个6*4的矩阵(6是总分片数,4是数据分片数,文中称之为Coding Matrix),和原始数据(Original Data)做乘法,得到一个新的矩阵(Coded Data):

Applying the Coding Matrix.png

可以看到,新的矩阵(Coded Data前四行和原始数据还是一样的,新增了两行不知道什么含义的数据。

现在,假设是34行数据被损坏了(索引从1开始)!

两行数据被损坏.png

那么,怎么由剩余的四行还原数据呢?

删除两行之后的等式.png

如上图,将Coding Matrix34行也去掉,这个等式依然成立。最重要的是,剩下的这个Coding Matrix是一个可逆矩阵(这个特殊的矩阵是怎么寻找出来的),等式两边同时乘以该矩阵的可逆矩阵:

两边同时乘以Coding Matrix的逆矩阵.png

最后总结,根据以下公式可以得到原始数据(由等式右边可以得到等式左边):

重建数据的公式.png

golang实现的RS库

github上有一个用golang实现的rs库

使用go get -u -v github.com/klauspost/reedsolomon 进行安装。

关键函数

查看他的doc文档,使用New方法可以得到一个实现了Encoder接口的对象。函数原型是func New(dataShards, parityShards int, opts ...Option) (Encoder, error),需要传入数据片和校验片的大小。

其中Encoder接口有以下几个关键的函数。

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

demo

官方提供了demo,学习一下simple-encoder.gosimple-decoder.go文件。

下面的代码做了一点修改。

simple-decoder.go:打开并读取D:/objects/文件夹下面的test文件(文件内容随便),使用rs库将其分为6个文件(4个数据片,2个校验片),保存在"D:/objects/encoder/"文件夹下。

文件内容如下(A,B,C,D分别20个),移动80个字节,注意不要使用windows的记事本进行编辑

AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDD

Enocder

simple-encoder.go代码如下:

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"

    "github.com/klauspost/reedsolomon"
)

const (
    dataShards = 4 // 数据分片数
    parShards  = 2 // 校验分片数
)


func main() {

    fname := "D:/objects/test"
    // Create encoding matrix.
    enc, err := reedsolomon.New(dataShards, parShards)
    checkErr(err)

    fmt.Println("Opening", fname)
    b, err := ioutil.ReadFile(fname)
    checkErr(err)

    // Split the file into equally sized shards.
    shards, err := enc.Split(b)
    checkErr(err)
    fmt.Printf("File split into %d data+parity shards with %d bytes/shard.\n", len(shards), len(shards[0]))

    // Encode parity
    err = enc.Encode(shards)
    checkErr(err)

    // Write out the resulting files.
    _, file := filepath.Split(fname)
    dir := "D:/objects/encoder/"
    for i, shard := range shards {
        outfn := fmt.Sprintf("%s.%d", file, i)

        fmt.Println("Writing to", outfn)
        err = ioutil.WriteFile(filepath.Join(dir, outfn), shard, os.ModePerm)
        checkErr(err)
    }
}

func checkErr(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error: %s", err.Error())
        os.Exit(2)
    }
}
Opening D:/objects/test
File split into 6 data+parity shards with 20 bytes/shard.
Writing to test.0
Writing to test.1
Writing to test.2
Writing to test.3
Writing to test.4
Writing to test.5

可以看到每个shard(分片)是20个字节,生成了6个文件,打开之后发现,test.0是20个Atest.120Btest.220Ctest.320D,正好可以拼接成完整的文件,和前面的理论符合。

Decoder

simple-decoder.go代码如下:

package main

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

    "github.com/klauspost/reedsolomon"
)

const (
    dataShards = 4
    parShards  = 2
)

func main() {
    // Create matrix
    enc, err := reedsolomon.New(dataShards, parShards)
    checkErr(err)

    fname := "D:/objects/encoder/test"
    // Create shards and load the data.
    shards := make([][]byte, dataShards+parShards)
    for i := range shards {
        infn := fmt.Sprintf("%s.%d", fname, i)
        fmt.Println("Opening", infn)
        shards[i], err = ioutil.ReadFile(infn)
        if err != nil {
            fmt.Println("Error reading file", err)
            shards[i] = nil
        }
    }

    // Verify the shards
    ok, err := enc.Verify(shards)
    if ok {
        fmt.Println("No reconstruction needed")
    } else {
        fmt.Println("Verification failed. Reconstructing data")
        err = enc.Reconstruct(shards)
        if err != nil {
            fmt.Println("Reconstruct failed -", err)
            os.Exit(1)
        }
        ok, err = enc.Verify(shards)
        if !ok {
            fmt.Println("Verification failed after reconstruction, data likely corrupted.")
            os.Exit(1)
        }
        checkErr(err)
    }

    outfn := "D:/objects/decoder/test"
    fmt.Println("Writing data to", outfn)
    f, err := os.Create(outfn)
    checkErr(err)

    // We don't know the exact filesize.
    err = enc.Join(f, shards, len(shards[0])*dataShards)
    checkErr(err)
}

func checkErr(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error: %s", err.Error())
        os.Exit(2)
    }
}
Opening D:/objects/encoder/test.0
Error reading file open D:/objects/encoder/test.0: The system cannot find the file specified.
Opening D:/objects/encoder/test.1
Error reading file open D:/objects/encoder/test.1: The system cannot find the file specified.
Opening D:/objects/encoder/test.2
Opening D:/objects/encoder/test.3
Opening D:/objects/encoder/test.4
Opening D:/objects/encoder/test.5
Verification failed. Reconstructing data
Writing data to D:/objects/decoder/test

删除test.0test.1这两个文件,执行程序,最后还是能正常的回复成原始数据。

残留问题

  • 具体算法还没有了解,如,Coding Matrix如何得到的。
  • 官方demo中还有stream-encoder.gostream-decoder.go没有阅读。
  • 该算法的一些限制,如数据分片和校验分配的个数有没有限制?

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,256评论 0 9
  • sina Bigtable 是一个分布式的结构化数据存储系统,它被设计用来处理海量数据:通常是分布在数千台普通服务...
    橙小汁阅读 3,555评论 0 4
  • 分布式系统面临的第一个问题就是数据分布,即将数据均匀地分布到多个存储节点。另外,为了保证可靠性和可用性,需要将数据...
    olostin阅读 4,559评论 2 26
  • ​​​本文主要介绍嵌入式系统的一些基础知识,希望对各位有帮助。 嵌入式系统基础 1、嵌入式系统的定义 (1)定义:...
    OpenJetson阅读 3,297评论 0 13
  • 失落与荣耀 教育科学学院 16小教理 候金冲 2017-2018赛季,足球还是足球,一样令人血脉贲张,一-样瞬...
    子黔子靇阅读 155评论 0 0