前言
各种存储系统的考验——由于组件失效而导致的数据丢失风险。比如,小的磁盘扇区出现静默数据错误,大到某个主机甚至整个机柜异常掉电。有些组件本身就具有容错机制。但应对场景有限比如磁盘消磁和遭受物理损坏等极端情况依旧无法恢复。所以需要在整个存储系统层面设计额外的数据保护机制,防止数据因为个别组件失效而遭受不可逆的损坏,导致整个系统无法正常运行。
纠删码(Erasure Coding,EC)是目前广泛使用的一种数据保护机制。主要通过对原始数据进行分片和编码,生成对应的备份数据,在把原始数据和备份数据写入不同的存储介质。数据恢复就是对分片编码的逆运算(所有要能使数据恢复,编码的函数必须是可逆的)。
纠删码最基础就是完全分片复制。为降低的存储空间成本,有高阶RAID(Redundant Array of Independent Disk,RAID5、RAID6),由RAID衍生,更具通用的RS-RAID(Reed-Solom RAID),通过更加复杂的编解码策略建立原始数据和备份数据之间的映射关系,使用计算代价减少需要备份的数据量,降低存储成本。
一、RAID技术概述
单个磁盘存取数据,有固有缺陷
-
访问速度慢:单一的I/O接口,无法实现并发
-
容量小:单个磁盘容量有限,无法满足爆炸式增长的数据存储需求
-
安全性差: 容易成单点故障,安全性差
RAID技术
RAID就是将多个物理磁盘抽象为一个容量更大的虚拟磁盘。
传统磁盘是以扇区为基本单位进行存储和访问
RAID是抽象出一个类似扇区的最小访问单位——条带。是一种虚拟化的方式,在多个磁盘之间建立一种逻辑映射关系。以条带作为基本单位进行管理
1.1 RAID0
最基础的RAID系统 ,之间将多个磁盘以条带为单位重新划分,形成一个地址空间在逻辑上连续的虚拟磁盘,其I/O和存储空间是所有磁盘之和,但没有容错能力。
1.2 RAID1
RAID1就相当于镜像,把同一份数据重复写入两个或多个磁盘,即每个磁盘存储的内容都是完全相同的。所以I/O能力和可用存储空间,只有所有磁盘之和的1/N,其中N为镜像个数。容错能力和镜像个数成正比。
1.3 RAID5
RAID5就是以一个或多个基本块作为条带深度均分数据,同时基于异或运算生成一个与数据块同等大小的校验块,所以能提供的I/O能力以及可用存储空间是所有磁盘的(N-1)/N,N为RAID5的磁盘数量,也是其条带宽度。RAID5有一定容错能力,允许一块磁盘异常,同时正常情况不需要读取,所以为了读带宽被浪费,RAID5条带中校验块位置不是不变的,而是在不同磁盘之间不同跳动。
1.4 RAID6
RAID6就是RAID5基础上提高容错能力,允许RAID整列有两个块磁盘故障。相对于RAID有两块磁盘故障。比RAID5多加了每个条带增加了一个校验块。每个条带包含两个校验块。空间和I/O利用率比RAID5要低,生成校验块算法更加复杂
综合来说,RAID0的速度更快,并且可以和其他RAID进行组合,实现更高级的RAID形式,常见组合有RAID10、RAID50。
二、RS-RAID和Jerasure
此处为语雀内容卡片,点击链接查看:www.yuque.com/go/doc/4950…
通过分析RAID(RAID5及以上)技术隐含的数学原理,推广至一般形式。
RAID5基于条带将数据均匀切分成多个数据块d;(i= 1, 2, .. n),然后尝试建立这些数据块之间的联系,例如使用普通的加法:
d.+d2+d3+... +dn=c
RAID5基于条带将数据均匀切分成多个数据块d;(i= 1, 2, .. n),然后尝试建立这些
数据块之间的联系,例如使用普通的加法:
d.+d2+d3+... +dn=c
上式中,c为基于加法生成的校验块。这样,如果任意一个数据块损坏(此时等式中的某个d;变为未知),都可以通过求解上述一-元一次方程进行数据恢复。然而上面这个方法却不会在实际中应用,原因在于普通的加法容易产生进位,因此为了记录所有数据块之和,校验块占用的存储空间--般情况下要大于参与计算的任--数据块的存储空间。对计算机而言,最好的替代方案就是采用布尔运算(这是计算机得以发明的基础之--),具体要替代普通的加法运算,则应该使用异或运算,这是因为:首先异或是基于位的运算(简单并且是计算机最高效的运算方式);其次,为了求解上述方程,需要对应算法的逆运算存在并且结果唯一,而异或运算的逆运算不但存在而且与正向运算完全相同;最后也最重要的是,异或运算不会产生进位,因此存储异或运算的结果并不需要比参与运算的任一对象更多的空间。
RAID6或者更高阶的RAID 原理上和RAID5类似,实际上是利用条带中n个数据块通过编码得到m个校验块( m为允许同时故障的最大磁盘数目),如果出现磁盘故障,通过编码的逆向过程(称为解码)可以还原得到所有缺失的数据块,从而实现数据恢复。
从数学上来说,上述问题可以转化为更一般的描述形式,即:如何基于n个可变输入,构造m个等式,使得对应的m元一次方程组有唯一解(当磁盘故障时,对应的输入变为未知数;容易理解这m个等式中至多包含m个未知数,否则对应的方程组可能有无穷多解,而不是唯一解)。lrving S. Reed和Gustave Solomon最早针对上述问题进行了研究,其结果是产生了一系列具有普遍意义、广泛应用于通信领域纠正信息传输过程中静默数据错误的Reed-Solomon codes。1997 年,James S.Plank将Reed-Solomon codes引人存储系统,用于实现高阶RAID,对应的RAID技术也称为RS-RAID,后者随之成为了RAID技术工业上的规范。
2.1 计算校验和
对一个对象有n数据块,分别为定义F为所有数据块的线性组合,则根据F,可以计算得到校验块
换言之,如果我们使用向量D和C分别表示所有数据块和校验块的集合,F表示矩阵F中的每一行,则整个编码的过程可以采用如下等式表示:
FD= C
因此,F也称为编码矩阵。RS-RAID使用mxn范德蒙德矩阵充当编码矩阵F,即有,于是上述等式变为:
2.2 数据恢复
进行数据恢复,需要定义矩阵A和向量E,满足(I为单位矩阵)并且
,于是
等式成立。
对于任何一个块,矩阵A和向量E都有一行与之对应,所以A也被成为分布矩阵,如果某个数据块失效,则相应把对应的行从矩阵A和向量E中删除的到新等式。
2.3 算术运算
RS-RAID,不能直接应用于计算机,因为数据恢复所依赖的高斯消元法需要用到除法运算,因为计算机的除法运算只能实现有限精度的除法运算,所以会导致结果不准确。计算计算的乘法运算会导致进位,导致结果变大,所需要的存储空间变大,不符合降低存储空间成本的需求。
对于计算机的进行的有限精度运算,需要定义一个符合二进制计算要求的整数集合
{0,1,1,...,2w-1}
这个整数集合有2W 个元素,当w=8时,这个集合就可以对应计算机一个字节(Byte)所能存储的数值范围。同时也需要定义一套基于该集合的运算法则,只需要包含加减乘除四则基本运算,计算结果要在集合内封闭。满足以上约束条件的集合为伽罗华域,也是有限域。数学表达形式为
RS-RAID具体实现中,中加法运算就是异或运算。加法和减法互为逆运算,异或运算的逆运算为自身,所以
中的减法运算与加法运算相同,也是异或运算。
比如
所以异或运算及逆运算在GF(2)内是封闭。
所以需要推导中乘法运算的一般形式,就需要定义GF(2)的多项式基本运算规则:
- 多项式系数全部来自GF(2)
- 多项式中次数相同的项,可以基于GF(2)中的加法进行合并。
假设:
所以
通过前面例子,定义多项式的模运算规则:
比如其中,s(x)和t(x)为任意多项式并且s(x)的次数小于q(x)
则:
假设:
计算过程:
所以:
只要多项式q(x)满足:
- 次数为w
- 所有的系数来自GF(2)
- 不能被因式分解
所以q(x)生成中所有元素,过程如下:
- 前三个元素为0,1,x
- 每次都是前一个元素乘以x,然后对q(x)取模。
- 重复步骤2,直到结果变为1.
因此,满足上述条件的多项式q(x)就是的生成多项式,记做:
常见的生成多项式:
针对与w=4,使用所有元素过程:
但在计算机中实现的RS-RAID中使用,需要把
中每一个元素与一个w位的二进制对应起来,所以需要把前面多项式每一项的系数和对应二进制数的对应的bit对应起来。
根据生成过程和四种表现形式可以推导出的乘法运算规则
- 首先要把乘数转换对应的多项式
- 进行性多项式乘法,对结果对q(x)取模
- 对结果转换为对应的二进制或十进制表示
也可以直接查表 :
引入对数性质
可以引入对数运算把普通的乘法和除法运算转换为加法、减法运算。
设gflog和gfilog分别为的对数运算和其逆运算,那么其乘法运算和除法运算可以简化为
如果能够预先计算出中所有元素中所有元素的gflog和gfilog表,就可以基于这两张表快速进行域中元素的乘法和除法运算。因为
中所有元素都由原生多项式
得来,又因为
根据上文的GF(2^4)的元素表示,生成gflog表和gfilog表如下:
就可以方便的推到出乘法和除法运算
就已经完整实现了基于的四则运算,RS-RAID在计算机中所需要的全部算术运算全部实现
2.4 Jerasure
纠删码是一类具有数据保护能力的编解码方法统称,RS-RAID也是纠删码一种具体实现。jerasure就是RS-RAID的开源实现。
jerrasure实现的纠删码是水平的,需要同时使用k+m块磁盘分别承载数据和校验数据(对应数据盘和校验盘)。同时对应的存储系统能容忍m块磁盘同时故障,纠删码使用的类型为MDS类型的纠删码。
此处为语雀内容卡片,点击链接查看:www.yuque.com/vaughan/ayo…
三、纠删码在Ceph中的应用
Ceph是基于存储池存储数据,按照数据保护方式存储池分为两类——多副本和纠删码。Ceph实现中,纠删码以插件形式提供服务的。
3.1 应用术语
3.1.1 块(chunk)
将对象基于纠删码进行编码时,每次编码将产生若干大小相同的块(参考上文,纠删码要求这些块是有序的,否则后续无法解码), Ceph 通过数量相等的PG将这些块分别存储至不同的OSD(磁盘)之中。每次编码时,序号相同的块总是由同一个PG负责存储。通过拼接所有数据块可以还原得到原始对象,通过校验块可以修复损坏的数据块。
3.1.2 条带(stripe)
如果待编码的对象太大,编码无法- -次完成,那么可以分多次进行,每次完成编码的部分称为一个条带。同一个对象内的条带是有序的,按照生成条带的顺序从0开始编号。
3.1.3 分片(shard)
同一个对象中所有序号相同的块位于同一个PG之上,它们组成对象的一个分片,分片的编号亦即块的序号。
3.1.4 其他
- k:条带中数据块个数。
- m:条带中校验块个数。
- n:条带中块个数,n=k+ m。
- rate:指空间利用率,可以通过k/n得到,例如k= 9, m=3,则空间利用率为9/12= 75%。
3.2 PG策略
存储池中对象到OSD的映射是通过PG来完成的。创建存储池过程中指定的PG数目实际上指的是逻辑PG数目。为了进行数据保护,Ceph 会将每个逻辑PG转化为多个PG实例(即一个逻辑PG实际上对应一个PG实例组),由它们负责将对象的不同备份(对应多副本)或者部分(对应纠删码)写人不同的OSD。上述过程是受控的,体现在以下两个方面:
1)每个逻辑PG具体被转化为多少个PG实例,由相应的数据保护策略决定。
例如使用多副本,那么每个逻辑PG会被转化为与副本个数相等的PG实例,因为此时每个PG实例保存的内容完全相同,所以原则上不需要对这些PG实例的身份进行区分。但是出于数据一致性考虑(针对同一个对象的读写一般是有序的,如果由不同的PG实例分别处理这些读写,因为数据备份策略一般要求这些PG实例分别位于不同主机(这是最小的容灾域)的OSD之上,所以很可能产生乱序,从而导致数据一致性问题。
针对纠删码,每个逻辑PG会被相应地转化为k + m个PG实例。与多副本不同,因为这些PG实例只保存每个对象的部分(参考上-节,每个PG实例只保存对象的一个分片),所以此时需要针对每个PG实例的身份进行严格区分,为其分配一个PG实例组内唯一的ID,称为Shard ID。和多副本类似,出于数据一致性考虑,也需要从这h+ m个PG实例中选举出一个“首领”PG,称为Primary Shard。
2)同一个逻辑PG产生的多个实例,通过存储池关联的CRUSH模板受控地分布在
位于不同容灾域的OSD之上,以实现指定级别的数据隔离和保护策略。因为纠删码进行数据映射时对于PG的顺序有严格要求,所以如果使用纠删码,则CRUSH返回的选择结果总是有序的,即使选不出足够的OSD完成PG映射亦是如此(此时对应的位置使用无效的OSD编号进行填充)。按照约定,Primary 或者Primary Shard固定由位于CRUSH返回的OSD序列中、第一个OSD上的PG实例充当。
3.3 新写
我们以典型的读写过程来分析纠删码的具体实现。我们首先介绍最简单的情形,即向存储池中写人一个全新的对象,为此,我们假定存在如下规格的纠删码存储池: k=3, m=2,图3-8展示了将-一个名为“NYAN"的全新对象写入该存储池的过程。
图3-8展示的是写操作最简单的一种情形,称为满条带写( Whole Stripe Write),即写人的数据范围正好覆盖对象的-一个或者多个完整条带。需要注意的是,针对同一个逻辑PG,将对象切分为多个分片分别写人不同的PG实例,以及从不同的PG实例读取所需要的分片最终拼凑出指定范围内的内容,都是由Primary Shard完成的,其他PG实例并不感知,这意味着每个PG实例都认为自己保存的是一个完整而独立的对象,因此其保存的内容在逻辑上是连续的,以块大小为单位,从0开始编址。仍然以图3-8为例,假定块大小为1个字节、条带大小为3个字节(条带大小总是等于k*块大小),则5个OSD最终都向名为“NYAN”的对象(但是对象的Shard ID不同)写入了3个字节,它们在对象内的逻辑地址范围相同,都是[0, 2]。
3.4 读
读操作与满条带写类似一Primary Shard收到客户端的读请求,首先将请求的逻辑地址范围进行条带对齐,据此计算得到每个PG实例需要读取的数据范围然后分别读取,最后再由Primary Shard统一汇总后向客户端应答。除此之外,针对读操作还有如下几个关键因素需要额外考虑:
(1)是否进行条带对齐
理论上正常情况下的读操作并不需要条带对齐,只要求每个PG实例能够读取指定范围内的数据即可。例如针对图3-8,假定块大小为1KB,待读取的数据范围为[512, 2560),
则正常情况下的读请求序列可能为:
PG(shard id = 0): [512, 1024)
PG(shard id = 1): [0,1024)
PG(shard id = 2): [0, 512)
假定PG从后端读取数据的最小粒度为一个扇区,可见此时不进行条带对齐可以使得开始和最后一个PG实例有机会少读部分数据。
然而实际上受限于所选择的纠删码算法,不可能将块大小设置得很大(例如Ceph默认将条带大小设置为4KB,此时块大小最大为4KB),这导致块大小经常会小于磁盘的最小访问粒度(例如BlueStore默认设置SSD的block-size 为4KB,机械磁盘的block-size为64KB),因此实际上不进行条带对齐能够取得额外收益的情形非常有限。同时,如果
PG处于降级状态(此时仍然可以正常进行读写)或者是Recovery触发的读操作,都有可能导致需要读取校验块进行数据修复后才能得到原始数据,而这个过程( 即解码)必须以块为基本单位进行。考虑到上面两个因素,在读操作真正开始之前,由Primary Shard预先进行条带对齐是合适的,因此上面这个例子中实际产生的读请求序列为:
PG(shardid = 0): [0,1024)
PG(shardid = 1): [0,1024)
PG(shardid = 2): [0,1024)
(2)是否需要对校验块的位置进行调整
前面已经提及:因为正常情况下并不需要读取校验块,所以RAID5需要对条带中校验块的位置进行动态调整,避免使用固定校验盘造成读带宽浪费。回到Ceph的纠删码实现:所有被映射到同一个逻辑PG下的对象,其每个分片总是被写人具有相同编号( ShardID)的PG实例,因为正常情况下PG实例并不会在OSD之间迁移,这意味着针对同一个逻辑PG而言,其数据盘和校验盘的位置几乎总是固定的。为什么Ceph可以采用这种方式而不必担心读带宽浪费呢?原因在于整个集群中不是只有一一个而是存在大量逻辑PG,而每个逻辑PG被转化为PG实例并最终分散至各个OSD的过程是随机的,这导致最终每个OSD既是一些逻辑PG的数据盘,同时也是另外一些逻辑PG的校验盘,因而不会造成读带宽浪费。
(3)是否仍然采用同步读
多副本实现中,因为副本间保存的内容相同,并且由Primary进行读写操作的数据一致性保证,所以所有读操作都由Primary直接在本地完成,这个过程是同步的。然而纠删码则不同,因为此时同一个对象的数据以分片的形式分布在多个PG实例之间,而这些PG实例一般由位于不同容灾域之下的OSD承载,这导致前端的单个读操作经常会被转化为节点之间的多个读操作,所以出于性能考虑,纠删码无法继续沿用同步读的方式。采用异步读虽然提升了读操作之间的并发度,有助于提升整体读带宽,但是因为涉及跨节点的读以及报文组装,所以原理上纠删码的读操作响应时延要比多副本高( 同时,与多副本相比,因为纠删码的写过程需要耗费大量的时间进行编码,所以一般而言纠删码的写操作响应时延也比多副本高),这使得纠删码不适合时延敏感类的应用。
(4)是否自动进行数据修复
纠删码的实现原理表明:当损失的数据块个数小于等于m时,总是可以通过解码还原得到所损失的全部数据块。因此,如果读的过程中检测到条带中的某些块(要求个数小于等于m)产生了数据错误,理论.上也可以在读的过程中针对这些坏掉的块同步进行修复,这个过程称为自动修复( auto-repair)。实现上能否执行自动修复主要取决于是否存在数据自校验机制,能否实时捕获静默数据错误,这是因为诸如链路不稳定、能耗变迁、负荷积压等因素都有可能导致磁盘短暂性的返回I/O错误,而只有产生静默数据错误时磁盘仍然工作在正常状态(按照定义,静默数据错误指磁盘自身无法感知的数据错误,因此静默数据产生时,磁盘仍然有可能处于“正常"状态),这是自动修复能够实施的基本条件。当前的纠删码实现中,因为作为默认对象存储引擎的FileStore 不具备数据自校验功能,所以默认不会进行自动修复(事实上,如果出现数据错误,那么Ceph采用的手段是直接终止相应的OSD进程,经过一段时间后如果OSD仍然没有恢复正常(即Ceph总是寄希望于通过人工干预来及时识别错误、解决错误),则将其踢出集群,同时将其承载的数据整体迁移至其他仍然正常的OSD。这种处理方式虽然简单粗暴,但是符合Ceph的设计理念,即Ceph是为管理大规模分布式集群而生,在这样的集群规模中,OSD乃至节点故障是一种常态,完全在意料之中因而是可接受的)。作为FileStore替代品的BlueStore意识到了这个问题,因此在BlueStore达到商用条件后,社区有望同步推出基于BlueStore的自动修复方案,以减少不必要的群体(OSD级别的)数据迁移。
3.5 覆盖写
基于上述基本读写流程,我们可以着手来分析一种更复杂的情形——覆盖写( overwrite)。顾名思义,覆盖写是针对对象的已有内容进行改写。对多副本而言,PG处理新写和覆盖写并无区别,然而纠删码则不然,纠删码通过引入条带的概念,将条带变成了更新对象数据的最小单位(这里指逻辑PG以条带为单位更新对象内的数据,对应到每个PG实例,则是以块为单位更新。纠删码的实现要求数据更新必须以条带为单位进行,即更新条带中任意数据块的同时必须同步更新校验块,否则后续无法进行数据恢复,或者说恢复的是错误的数据),因此如果覆盖写的起始或者结束地址没有进行条带对齐,那么对于不足一个完整条带的部分,其写人只能通过“读取完整条带→修改数据+基于条带重新计算校验数据一+写入(被修改部分和校验和)”这样的步骤来进行,这个过程被称为RMW ( Read Modify Write)。RMW过程中的R,即读是为了和待改写的内容-一起, 再次拼凑出一个完整的条带,以完成纠删码所要求的重新编码计算(目的是更新校验块),因此这个读也称为补齐读,容易理解整个RMW过程中补齐读阶段最为耗时,因此为了提升覆盖写的性能,通常有两种思路: - -是尽量减少RMW数量;二是如果RMW不可避免,则需要尽量减少补齐读所读取的数据量。
引人写缓存是减少RMW数量的一种常见方法。仍以图3-8为例,假定块大小为1KB,条带大小为3KB,并且存在如下的覆盖写序列:
write1:[0,4096)
write2: [4096, 8192)
write3: [8192, 12288)
如果不做任何处理,那么结果是上述序列中的每3个原始写请求都将被转化为2个
满条带写和4个RMW写,如图3-9所示。
反之,如果引入缓存,通过对所有仍然驻留在缓存中的写操作进行合并,我们可以有效减少RMW数目。例如假定每个写操作在缓存中驻留的时间足够长,则上面这个例子中类似write1、write2、 write3 这样的三个写操作最终会被聚合成为一一个单独的写请求:
write:[0,12288)
3.5 日志
针对纠删码而言,无论是新写还是覆盖写,本质上都是一种分布式写, 因此需要使用日志(这里指PG级别的日志)来保证PG实例之间的数据一致性。例如当系统整体掉电时如果还有写尚未完成,则或者通过日志将数据回滚到写之前的状态(对应尚未向客户端发送写人完成应答的场景),或者通过日志重放继续执行(也称为前滚( rollforward),
对应已经向客户端发送过写人完成应答的场景)。
对多副本而言,因为每个副本保存的内容完全相同,这意味着只要任意-一个副本数据更新成功,后续就可以基于这个副本将其他副本的数据同步至最新,所以多副本基于日志进行数据恢复时,总是倾向于选择同-一个逻辑PG中包含最新日志的那个PG实例,以此作为基准,通过简单的日志前滚操作即可完成损坏对象的修复。
纠删码则不然,当覆盖写发生时,因为纠删码每个逻辑PG对应的PG实例中保存的都是原始对象独- -无二的内容(即分片),所以为了防止将原始对象写坏,实现上需要每个PG实例在覆盖写发生之前先通过克隆操作将待执行覆盖写的内容进行备份,等待后续覆盖写真正完成之后再删除备份。相应的,纠删码日志需要如实记录上述数据备份过程,以便系统从异常中恢复后,如果判定某些写操作无法通过数据修复继续完成(例如采用4+ 2模式,假定某次写操作仅成功写人3个分片,由纠删码的特性我们知道基于这3个存活的分片并不足以还原出完整的、待写人的原始数据用于后续执行数据修复),则通过日志将对象回滚至前一个原子状态以保证数据一致性。
四、总结
作为一种早在20世纪90年代就已经在存储系统中普及的数据保护技术,纠删码并非是一种新生事物。纠删码以其灵活多变的数据备份策略较高的存储空间收益深受“廉价”存储系统的青睐,非常适合于存储大量对时延不敏感的“冷” 数据(例如备份数据)。
1 )相较多副本而言,纠删码实现更复杂。
纠删码以条带为单位,通过数学变换,将采用任意k + m备份策略所消耗的额外存储空间都成功控制在1倍以内(与之相比,具有同等安全系数的多副本策略则要消耗m倍的额外存储空间),代价是大大增加了系统的复杂性。对Ceph而言,与多副本类似,纠删码条带中的每个块都需要通过PG存储至位于不同容灾域中的OSD之上来保证数据可靠性,这意味着在纠删码类型的存储池中,为了获得同样的可靠性,一般每个逻辑PG映射出来的PG实例数目要比多副本存储池多,从而给基于PG的数据同步和一致性带来了更大的挑战。
2 )相较多副本而言,纠删码性能更差。
在存储系统中,读性能的表现至关重要。Ceph 基于CS的访问模式使得其I/O路径相较传统存储本来就偏长,而其分布式的天性进一步的使得任何客户端的读都会被纠删码转化为集群内多个跨节点的读,并且产生跨节点读的个数和k值成正比。因为跨节点的传输时延和协同消耗使得纠删码的读性能必然要比同等条件下的多副本差,再加上生产环境中为了取得更高的空间收益(对应更大的k值)和更高的安全系数(对应更大的m值,因为纠删码要求k≥m,所以m值增加的同时也意味着要求配置更高的k值)往往要求我们配置更大的k值和m值,这反过来又会使读性能变得更差,所以一般而言纠删码的读性能(例如I/O平均时延)很少能满足生产环境的要求。此外,纠删码在设计上使用条带作为数据更新的最小单位,而出于编解码效率考虑,我们无法将条带设计得很小,因此针对覆盖写的场景,纠删码非常容易产生大量的RMW操作,这使得纠删码的综合写性能也比多副本要差。兴许纠删码最适合的应用场景是永远只进行追加写或者删除,