Ceph学习记录7-纠删码

参考:Ceph源码分析 常涛


1. EC的基本原理

云存储领域比较流行的数据冗余存储方法,原理和传统的RAID类似,但是比RAID灵活。

它将写入的数据分成N份原始数据,通过这N份原始数据计算出M份校验数据。把N+M份数据分别保存在不同的设备或者节点中,并通过N+M份中的任意N份数据块还原出所有的数据块。

EC包含了编码和解码的两个过程:

编码:将原始的N份数据计算出M校验数据。

解码:通过这N+M份数据中的任意N份数据来还原出原始数据的过程称为解码。

EC可以容忍M份数据失效,任意小于等于M份的数据失效能通过剩下的数据还原出原始数据。

目前,一些主流的云存储商都采用EC编码方式:

Google GFS II---RS(6,3)编码

Facebook HDFS RAID---RS(10,4)编码

Mircosoft Azure---LRC(12,2,2)编码

2. EC的不同插件

ceph支持以插件形式来指定不同的EC编码方式。各种编码的不通电,实质上就是在ErasureCode的三个指标之间折中的结果,这三个指标是:空间利用率、数据可靠性和恢复效率。

2.1 RS编码

目前最广泛的纠删码是ReedSolomon编码,简称RS码。

RS编码实现之一:Jerasure,是一个ErasureCode开源实现库,实现了EC的RS编码,目前ceph中默认的就是Jerasure方式。

RS编码实现之二:ISA,是Intel提供的一个EC库,只能运行在Intel CPU上,它利用了Intel处理器本地指令来加速EC的计算。

RS编码不足之处:在N+K个数据块中有任意一块数据失效,都需要读取N块数据来恢复丢失的数据。在数据恢复的过程中引起的网络开销比较大。因此,LRC编码和SHEC编码分别从不同角度做了相关优化。

2.2 LRC

LRC编码的核心思想为:将校验块(parity block)分为全局校验块(global parity)和局部校验块(local reconstruction parity),从而减少恢复数据的网络开销。其目标在于解决当单个磁盘失效后恢复过程的网络开销。

LRC(M,G,L)的三个参数分别为:

M:原始数据块的数量

G:全局校验块的数量

L:为局部校验块的数量

编码过程为:把数据分为M个同等大小的数据块,通过该M个数据块计算出G份全局校验数据块。然后把M个数据块平均分为L组,每组计算出一个本地数据校验块,这样共有L个局部数据校验块。

下面以Azure的LRC(12,2,2)和Facebook的HDFS RAID的早起编码方式RS(10,4)为例来比较LRC和RC编码在恢复过程的开销:

LRC(12,2,2) RS(12+4)

D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12

                    L1                  L2

                           G1  G2

D1~D12 P1~P4

表中LRC编码:总共有12 个数据块,分别为D1~D12.有两个本地数据校验块L1和L2,L1为通过第一组数据块D1~D6计算而得到的本地校验数据块;L2为第二组数据块D7~D12计算而得到的本地校验数据块。有两个全局数据校验块G1和G2,它是通过所有数据块D1~D12计算而来。对应RS编码,数据块D1~D12,计算出的校验块为P1~P4.

不同情况下的数据恢复开销:

***如果数据块D1~D12只有一个数据块损坏,LRC只需要读取6个额外的数据块来恢复。而RS需要读取12个其他的数据块来修复。

***如果L1或者L2其中一个数据块损坏,LRC需要读取6个数据块。如果G1,G2其中一个数据损坏,LRC扔需要读取12个数据块来修复。

最大允许失效的数据块:

***RS允许数据块和校验块中任意的小于等于4个数据的失效。

***LRC:

            数据块中,只允许任意的小于等于2个数据块失效。

           允许所有的校验块(G1,G2,L1,L2)同时失效。

            允许至多两个数据块和两个本地校验块同时失效。

综上分析:对于只有一个数据块失效,或者一个本地数据校验块失效的情况下,再恢复该数据块时,LRC比RS可以减少一般的磁盘IO和网络带宽。所以LRC重点在单个磁盘失效后恢复的优化。但是对于数据可靠性来说,通过最大允许失效的数据块个数的讨论可知,LRC会有一定的损失。

2.3 SHEC编码

 SHEC的编码方式为SHEC(K,M,L),其中K代表data chunk的数量,M代表parity chunk的数量,L代表计算parity chunk时所需要的data chunk数量。其最大允许失败的数据块为ML/K. 这样恢复失效的单个数据块只需要额外读取L个数据块。

以SHEC(10,6,5)为例,其最大允许失效的数据块为:

M(6) * L(5) / K(10) = 3

D1~D10为数据块

P1:D1~D5计算出的校验块

P2:D3~D7计算出的校验块

P3:D5~D9

P4:D6~D10

P5:D1~D2  D8~D10

P6:D1~D4  D10

2.4 EC和副本的比较

  三副本 RS(10,4) LRC(10,6,5) SHEC(10,6,5)
数据容量开销 3X 1.4X 1.8X 1.6X

数据恢复开销

(单个数据块失效)

1X 10X 5X 5X
可靠性 中下

说明:

***在三副本的情况下,恢复效率和可靠性都比较高,缺点就是数据容量开销比较大

***对于EC的RS编码,和三副本比较,数据开销显著降低,以恢复效率和可靠性为代价。

***LRC编码以数据容量开销率高的代价,换取了数据恢复开销的显著降低。

***SHEC编码用可靠性换代价,在LRC的基础上进一步降低了容量开销。


3. Ceph中EC的实现方式

3.1 基本概念

首先介绍一些EC的基本概念。注意,这里提到的stripe是RADOS系统定义的,可能与其他系统的定义不同。

***chunk:一个数据块就叫data chunk,简称chunk,其大小为chunk_size设置的字节数。

***stripe:用来计算同一个校验块的一组数据块,称为data_stripe,简称stripe,其大小为stripe_width,参与的数据块的数目为stripe_size.

stripe_width = chunk_size * stripe_size

如图:一个EC(4+2):stripe_size = 4, chunk_size = 1K, 那么stripe_width = 4K.

在ceph中,默认stripe_width就是4K.

3.2 EC支持的写操作

当前ceph的EC写入还有一定的限制,目前支持的操作如下:

***create object:创建对象

***remove object:删除对象

***write full:写整个对象

***append write(stripe width aligned):追加写入(限定追加操作的起始偏移以stripe_width对齐)

目前ceph只支持上述操作,而不支持overwrite操作,其主要有如下两个条件的限制:

***由于编码和解码的过程都以stripe width整块数据计算

***EC在特殊场景需要回滚场景

所以,目前EC只支持append写操作中,写操作的起始偏移offset以stripe_width对齐的情况,如果ennd不是以stripe_width对齐,就补0对齐即可。

目前不支持以下情况:

***append写操作,写操作的起始偏移offset没有以stripe_width对齐。

***overwrite写操作,offset和end都不以stripe_width对齐。

(由于计算数据校验块需要读取整个stripe的数据块,所以前两种情况都需要读取该stripe确实的数据块,来计算校验块,由于性能原因,目前不支持)

***overwrite写操作,写操作的起始偏移offset和结束为止end都以stripe_width对齐。

(overwrite写操作不支持是由EC的回滚机制导致的)

3.3 EC的回滚机制

依据EC原理可知,EC(N+M)的写操作如果小于等于M个OSD失效,不会导致数据丢失没数据可恢复。EC在理论上就最多只能容忍M个OSD失效。如果OSD失效的数量大于M,这种情况就超出了理论设计的范畴,系统无法处理这种情况。可以说这是合理的。

但是对于所有的存储系统,必须应对一种特殊情况:整个机房或者整个数据中心全部断电,系统重启后可以恢复,并且数据不丢失。

当存储烯烃全局断电时,其数据的写入状态就有可能出现:小于N个磁盘的数据成功写入,而其他磁盘没有写成功的情况。

以之前EC(4+2)为例,假设写操作只有三个OSD写成功了,其他的3个OSD没有来得及把数据写入磁盘。这种情况下,不但导致新数据写入失败,而且导致旧数据也无法读取成功。这就需要EC的回滚机制,回滚到最后一次成功写入的旧数据版本。

Ceph目前支持的EC操作都是回滚比较容易实现的,实现机制如下:

***create object操作的回滚实现比较简单,删除该对象即可

***对于remove object操作,在执行时并不删除该对象,而是暂时保留该对象;如果需要回滚,就可以直接恢复。

***writeFull操作,暂时保留旧的对象,创建一个新的对象完成写操作。当需要回滚时,恢复旧的数据对象。

***append操作,记录append时的size到PG日志中,当需要回滚时,对该对象做truncate操作即可。


4. EC源码分析

对应EC的上述三种变更操作,其本地回滚的信息都记录在对应的PG日志记录的mod_desc中:

struct pg_log_entry_t{
.......
    objectModDesc mod_desc;
......
};

在函数ReplicatedPG::do_osd_ops中实现操作的事务封装,下面重点分析EC的写操作和write_full操作的实现。

4.1 EC的写操作

首先验证如果是EC类型,写操作的offset必须以stripe_width对齐,否则不支持。

case CEPH_OSD_OP_WRITE:
    if(pool.info.requires_aligned_append() &&
        (op.extent.offset % pool.info.required_alignment() != 0)) {
            result = -EOPNOTSUPP;
            break;
}

如果对象不存在,就在mod_desc中添加创建的信息,否则在mod_desc中添加old size的信息:

ctx->mod_desc.create();

 否则就追加写:

ctx->mod_desc.append(oi.size);

最后把写操作添加到事务中:

if(pool.info.require_rollback())
    t->append(soid, op.extent.offset, op.extent.length, osd_op.indata, op_flags);

4.2 EC的write_full

如果对象已经存在,调用函数ctx->mod_desc.rmobject,如果返回false,说明已经记录了信息,直接删除,如果返回true,就调用stash保存旧的对象数据,用来恢复:

case CEPH_OSD_OP_WRITEFULL:
   ......
   if(obs.exists){
        if(ctx->mod.desc.rmobject(ctx->at_version.version)) {
            t-stash(soid, ctx->at_version.version);
         }else{
            t->remove(soid);
         }
    }

在事务中写入数据:

t->append(soid, 0, op.entent.length, osd_op.indata, op.flags);

 4.3 ECBackend

类ECBackend实现了EC的读写操作。ECUtil里定义了编码和解码的函数实现。ECTransaction定义了EC的事务。


目前纠删码的研究是一个热点,它可以极大的提供存储利用率,降低存储成本。目前研究都在着力研究纠删码如何直接支持块存储,也就是随机overwrite操作的能力。

猜你喜欢

转载自blog.csdn.net/qq_20283969/article/details/83240580