Ceph 進階系列(四):Ceph的糾刪碼特性 EC(Erasure Code)

從GitHub上Clone Ceph項目,我是基於(ceph version 12.2.11 luminous 版本)的代碼來分析的

一、EC(Erasure Code)是什麼?

Ceph的糾刪碼特性EC:將寫入的數據分成N份原始數據,通過這N份原始數據計算出M份效驗數據。把N+M份數據分別保存在不同的設備或者節點中,並通過N+M份中的任意N份數據塊還原出所有數據塊。EC包含了編碼和解碼兩個過程:將原始的N份數據計算出M份效驗數據稱爲編碼過程;通過這N+M份數據中的任意N份數據來還原出原始數據的過程稱爲解碼過程。EC可以容忍M份數據失效,任意小於等於M份的數據失效能通過剩下的數據還原出原始數據。Ceph支持以插件的形式來指定不同的EC編碼方式。不同的EC編碼方式是三個指標間的折中結果,這個三指標就是是:空間利用率、數據可靠性和恢復效率。

二、不同的 EC 編碼方式

1. RS類型編碼, 目前應用最廣泛的糾刪碼是ReedSolomon編碼,簡稱RS碼。下面是RS編碼的兩個實現(ISA + Jerasure):

1). .ISA:ISA是Intel提供的一個EC庫,只能運行在Intel CPU上,它利用了Intel處理器本地指令來加速EC的計算。

2). Jerasure是一個ErausreCode開源實現庫,它實現了EC的RS編碼。目前Ceph中默認的編碼就是Jerasure方式

RS編碼的不足之處在於:在N+K個數據塊中有任意一塊數據失效,都需要讀取N塊數據來恢復丟失數據。在數據恢復的過程中引起的網絡開銷比較大。因此,LRC編碼和SHEC編碼分別從不同的角度做了相關優化。

2. LRC類型編碼(特點:恢復數據塊時,減少了讀取網絡數據塊的數量)

LRC編碼的核心思想爲:將校驗塊(parity block)分爲全局校驗塊(global parity)和局部校驗塊(local reconstruction parity),從而減少恢復數據的網絡開銷。

LRC(M,G,L)的三個參數分別爲:
·M是原始數據塊的數量。
·G爲全局校驗塊的數量。
·L爲局部校驗塊的數量。

編碼過程爲:把數據分成M個同等大小的數據塊,通過該M個數據塊計算出G份全局效驗數據塊。然後把M個數據塊平均分成L組,每組計算出一個本地數據效驗塊,這樣共有L個局部數據校驗塊。

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,且當一個數據塊失效時,只讀取5個數據塊就可以恢復。

三、糾刪碼 EC 和 副本 Replicated 的比較

衆所周知在創建Ceph的pool時,可以設置pool的冗餘恢復方式,EC類型或者Replicated副本類型。指定EC類型時,可以設置N和M的參數。各種糾刪碼(EC的) 和 副本(Replicated)的比較如下表所示:

說明如下:
·在副本類型(三副本)的情況下,恢復效率和可靠性都比較高,缺點就是數據容量開銷比較大。
·EC的RS編碼,和三副本比較,數據開銷顯著降低,以恢復效率和可靠性爲代價。
·EC的LRC編碼以數據容量開銷略高的代價,換取了數據恢復開銷的顯著降低。
·EC的SHEC編碼用可靠性換代價,在LRC的基礎上進一步降低了容量開銷。 

、先來過一下OSD 處理寫操作的序列圖,後面分析的EC寫流程都是走這個框架

 

根據上面的OSD序列圖來分析一下execute_ctx裏發生了什麼。execute_ctx的函數調用關係爲:

PrimaryLogPG::execute_ctx(OpContext *ctx)

     => PrimaryLogPG::prepare_transaction(OpContext *ctx)                                    //準備transaction

            => PrimaryLogPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)    //填充ctx變量的相關成員

    => PrimaryLogPG::issue_repop(RepGather *repop, OpContext *ctx)

            => PGBackend::submit_transaction(...)                                                         //提交transaction給PGBackend,見上圖          

五、EC 寫操作源代碼的分析   

舉例EC寫操作的代碼流程分析,來看相關的函數和數據結構

1. 下面分析EC的寫操作時,函數PrimaryLogPG::do_osd_ops中實現操作的事務封裝

//源代碼文件 src/osd/PrimaryLogPG.cc
int PrimaryLogPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)
{
...
PGTransaction* t = ctx->op_t.get();
...

1)多處代碼都驗證如果是EC類型,寫操作的offset必須以stripe_width對齊,否則不支持。

////源代碼文件 osd_types.h裏定義的requires_aligned_append() 函數判斷POOL是否是EC類型

/*
 * pg_pool
 */
struct pg_pool_t {
... 
 bool requires_aligned_append() const {
    return is_erasure() && !has_flag(FLAG_EC_OVERWRITES);
  }
...
}

////源代碼文件 src/osd/PrimaryLogPG.cc裏 do_osd_ops() 函數的 CEPH_OSD_OP_WRITE 寫操作:

// --- WRITES ---
// -- object data --
case CEPH_OSD_OP_WRITE:
      ++ctx->num_write;
    ...
    if (pool.info.requires_aligned_append() &&
    (op.extent.offset % pool.info.required_alignment() != 0)) {
        result = -EOPNOTSUPP;
        break;
    }

2)如果對象不存在,do_osd_ops() 函數裏調用PrimaryLogPG::maybe_create_new_object來創建

maybe_create_new_object(ctx);

來看看PrimaryLogPG::maybe_create_new_objec()函數的定義

void PrimaryLogPG::maybe_create_new_object(
  OpContext *ctx,
  bool ignore_transaction)
{
  ObjectState& obs = ctx->new_obs;
  if (!obs.exists) {
    ctx->delta_stats.num_objects++;
    obs.exists = true;
    assert(!obs.oi.is_whiteout());
    obs.oi.new_object();
    if (!ignore_transaction)
      ctx->op_t->create(obs.oi.soid);
  } else if (obs.oi.is_whiteout()) {
    dout(10) << __func__ << " clearing whiteout on " << obs.oi.soid << dendl;
    ctx->new_obs.oi.clear_flag(object_info_t::FLAG_WHITEOUT);
    --ctx->delta_stats.num_whiteouts;
  }
}

3)最後把寫操作添加到事務中(t是一個PGTransaction類型的變量,通過ctx->op_t.get()): 

    	if (op.extent.length == 0) {
	  ...
	} else {
	  t->write(
	    soid, op.extent.offset, op.extent.length, osd_op.indata, op.flags);
	}

2. 在函數PrimaryLogPG::do_osd_ops實現事務封裝後,由PGBackend提交整個操作上下文信息OpContext ctx給FileStore/BlueStore

PrimaryLogPG::execute_ctx(OpContext *ctx) => PrimaryLogPG::issue_repop(RepGather *repop, OpContext *ctx) => PGBackend::submit_transaction(...),其中PGBackend::submit_transaction爲虛函數,具體函數由子類ReplicatedPGBackend/ECPGBackend實現,該函數submit_transaction的參數如下:
PGBackend::submit_transaction(
  const hobject_t &hoid,
  const object_stat_sum_t &delta_stats,
  const eversion_t &at_version,
  PGTransactionUPtr &&t,
  const eversion_t &trim_to,
  const eversion_t &roll_forward_to,
  const vector<pg_log_entry_t> &log_entries,
  boost::optional<pg_hit_set_history_t> &hset_history,
  Context *on_all_commit,
  ceph_tid_t tid,
  osd_reqid_t reqid,
  OpRequestRef client_op
  )

3. EC*類介紹

 類ECBackend實現了EC的讀寫操作。ECUtil裏定義了編碼和解碼的函數實現。ECTransaction定了EC的事務      

參考:《Ceph 源代碼分析》     

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章