这是我参与「第四届青训营 」笔记创作活动的第3天
1 服务需求
- 故障不可避免,需要保证数据安全尽量避免缺失
2 EC纠删码算法
传统的硬盘级RAID模式将数据存放于单节点内的不同硬盘,当整节点发生故障时,无法有效恢复数据。为了避免数据丢失,存储系统需要将数据在节点间进行冗余保护。Erasure Coding(简称EC,即纠删码)是一种冗余保护机制,通过计算校验片的方式实现数据冗余保护。
分布式存储系统在写入数据时,将数据切分为N个数据块(N为偶数),通过EC编码算法计算得到M个校验块(M取值2、3或4)。
- 服务器级安全:将N+M个数据块和校验块存储于不同的节点中,故障M个节点或M块硬盘,系统仍可正常读写数据,业务不中断,数据不丢失。
- 机柜级安全:将N+M个数据块和校验块存储于不同的机柜中,故障M个机柜、不同机柜的M个节点或M块硬盘,系统仍可正常读写数据,业务不中断,数据不丢失。
- EC冗余方式的空间利用率约为N/(N+M),N越大,空间利用率越高,数据的可靠性由M值的大小决定,M越大可靠性越高。 Erasure Code是一种编码技术,它可以将n份原始数据,增加m份数据,并能通过n+m份中的任意n份数据,还原为原始数据。即如果有任意小于等于m份的数据失效,仍然能通过剩下的数据还原出来。纠删码技术在分布式存储 系统中的应用主要有三类,阵列纠删码(Array Code: RAID5、RAID6等)、RS(Reed-Solomon)里德-所罗门类纠删码和LDPC(LowDensity Parity Check Code)低密度奇偶校验纠删码。
3 golang库
reedsolomon是业界常用的Reed Solomon算法,本文主要介绍如何使用该库。
3、1 写数据
enc, err := reedsolomon.New(3, 2)
if err != nil {
panic(err)
}
file, err := ioutil.ReadFile("hello.txt")
log.Println("hello.txt的长度为:", len(file))
if err != nil {
panic(err)
}
split, err := enc.Split(file)
if err != nil {
panic(err)
}
fmt.Printf("File split into %d data+parity shards with %d bytes/shard.\n", len(split), len(split[0]))
err = enc.Encode(split)
if err != nil {
panic(err)
}
dir, f := filepath.Split("hello.txt")
for i, s := range split {
out := fmt.Sprintf("%s.%d", f, i)
fmt.Println("Writing to", out)
err = ioutil.WriteFile(filepath.Join(dir, out), s, 0644)
if err != nil {
panic(err)
}
}
其核心代码是split, err := enc.Split(file)新建一个enc后即可将数据进行切片,然后依次写入文件中,使用起来也是非常方便的。
3、2 读数据
encoder, err := reedsolomon.New(3, 2)
if err != nil {
panic(err)
}
data := make([][]byte, 3+2)
for i := 0; i < 5; i++ {
filename := fmt.Sprintf("%s.%d", "hello.txt", i)
file, err := ioutil.ReadFile(filename)
if err != nil {
continue
}
data[i] = file
}
verify, err := encoder.Verify(data)
if err != nil {
panic(err)
}
if !verify {
err := encoder.Reconstruct(data)
if err != nil {
panic(err)
}
b, err := encoder.Verify(data)
if !b {
fmt.Println("Verification failed after reconstruction, data likely corrupted.")
os.Exit(1)
}
}
file, err := os.Create("test.txt")
if err != nil {
panic(err)
}
err = encoder.Join(file, data, len(data[0])*3)
if err != nil {
panic(err)
}
读数据核心代码是verify, err := encoder.Verify(data)将可以读取到的数据进行校验,如果校验不通过也可以通过encoder.Reconstruct(data)进行数据恢复,这是一种跨节点的操作。
3、3 多副本策略
副本,顾名思义就是多个数据副本,简单来说就是一个数据拷贝多份完全一样的副本,分别存放在多个不同节点上。比如我们常用的3副本(如下图所示)就是将A这个数据拷贝了3份,分别存放在节点1、3、4上,这三个节点是在整个集群中随机选择的,下一个B数据有可能就放在节点1、2、4上了。
3、4 多副本策略与EC算法对比
多副本(N)与纠删码(M+N)
- 可用容量:多副本 1/N较低,EC M/(M+N),较高
- 读写性能:多副本较高,EC较低,小块IO尤其明显
- 重构性能:副本无校验计算,较快 EC有校验计算,较慢
- 容忍节点故障数量: N-1 N
- 适用场景: 块存储,小文件 大文件 综合来看,如果用户更关注性能,尤其是小IO的场景,多副本往往是更好的选择,如果用户更关注可用容量,而且是大文件场景的话,纠删码会更合适。