1. 程式人生 > >Ceph學習記錄7-糾刪碼

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操作的能力。