1. 程式人生 > >ceph 原始碼分析 — peering 過程

ceph 原始碼分析 — peering 過程

本人最近仔細研究ceph 恢復部分的原始碼,這個閱讀分析的過程比較艱難,分享在此,希望大家能互相交流學習,有不正確的地方,希望大家指正!

Peering的作用

Peer的過程,是使一個PG內的OSD達成一個一致狀態,當主從副本完成達成一個一致的狀態,peering 的狀態就結束,PG處於active狀態。但此時,該PG的三個OSD的資料副本並非完全一致。

基本概念

acting set 和 up set

acting set 就是一個pg對應的副本所在的osd 列表,列表是有序的,第一個osd 為 primary. 在通常情況下,up set和acting set 相同,要理解不同之處,需要理解pg_temp

pg temp

如果pg的acting set 為[0,1,2], 此時如果osd.0出現故障,導致CRUSH演算法重新分配該pg的acting set 為[3,1,2]. 此時osd.3為該pg的主osd,但是osd.3並不能負擔該pg的讀操作,因為其上現在還沒有資料。所以向monitor申請一個臨時的pg,osd.1為臨時主osd,這時acting set依然為[0,1,2],up set 變為[1,3,2],此時就出來acting set 和up set的不同。當osd.3 backfill完成之後,該pg的up set恢復為acting set, 也就是acting set 和 up set都為[0,1,2]

current interval 和 past_interval

都是epoch的一個序列,在這個序列之內,一個pg的acting set 和 up set不會變化。current是當前的序列,past是上一個階段的序列。
last_epoch_started: pg peering完成之後的epoch
last_epoch_clean: pg recovery完成,處於clean狀態的epoch

例如:
ceph 系統當前的epoch值為20, pg1.0 的 acting set 和 up set 都為[0,1,2]

  • osd.3失效導致了osd map變化,epoch變為 21
  • osd.5失效導致了osd map變化,epoch變為 22
  • osd.6失效導致了osd map變化,epoch變為 23
    上述三次epoch的變化都不會改變pg1.0的acting set和up set
  • osd.2失效導致了osd map變化,epoch變為 24
    此時導致pg1.0的acting set 和 up set變為 [0,1,8],若此時 peering過程成功完成,則last_epoch_started 為24
  • osd.12失效導致了osd map變化,epoch變為 25
    此時如果pg1.0完成了recovery,處於clean狀態,last_epoch_clean就為25
  • osd13失效導致了osd map變化,epoch變為 26
    epoch 序列 21,22,23,23 就為pg1.0的past interval
    epoch 序列 24,25,26就為 pg1.0的current interval

pg log

儲存了該pg內所有更新操作的日誌記錄,具體記錄的欄位,可以檢視struct pg_log_entry_t,這裡列出關鍵的欄位:

  • __s32 op; 操作的型別
  • hobject_t soid; 操作的物件
  • eversion_t version, prior_version, reverting_to; 操作的版本

需要注意的是,pg log的記錄是以整個PG 為單位,而不是以物件為單位

PG info

關於pg在osd上的一些元資料資訊,具體儲存struct pg_info_t 資料結構裡
列舉一些重要的欄位:

  • eversion_t last_update; //pg 最後一次更新的eversion
  • eversion_t last_complete; //recovery完成後的最後一個everson,也就是pg處於clean狀態的最後一次操作的 eversion
  • epoch_t last_epoch_started;// last epoch at which this pg started on this osd 這個pg在這個osd上的最近的的開始的epoch,也就是最近一次peering完成後的epoch
  • version_t last_user_version; // last user object version applied to store
  • eversion_t log_tail; // 日誌的尾,也就是日誌最老的日誌記錄
  • hobject_t last_backfill; // objects >= this and < last_complete may be missing
    最後一個backfill 操作的物件,如果該osd的backfill沒有完成,那麼last_backfill 和last_complete之間的物件就丟失

權威日誌,在程式碼裡簡寫為olog. 某個pg的一個完整的,順序的操作日誌。
eversion_t head; // 日誌頭,儲存最新寫入的日誌
eversion_t tail; // 日誌尾,最後早寫入的日誌

up_thru

這個實在不適合特別理解的概念

Peering的時機

當系統初始化時,或者有OSD 失效,或者OSD增加或者刪除,導致OSD map發生變化,這引起PG的 acting set的變化,該PG會發起Peering過程。

Peering的基本過程

Peering的過程,基本分為三個步驟

  • GetInfo : pg的主osd通過傳送訊息獲取各個從OSD的pg_info資訊
  • GetLog:根據pg_info的比較,選擇一個擁有權威日誌的osd(auth_log_shard) , 如果主osd不是擁有權威日誌的osd,就去該osd上獲取權威日誌
  • GetMissing:拉取其它從OSD 的pg log(或者部分獲取,或者全部獲取FULL_LOG) , 通過本地的auth log對比,來判別從OSD 上缺失的object 資訊。以用於後續recovery過程的依據.
  • Active: 啟用主osd,併發想通知notify訊息,啟用相應的從osd

GetInfo

pg_info_t

struct pg_info_t {
  spg_t pgid;
  eversion_t last_update;  //pg 最後一次更新的eversion
  eversion_t last_complete;  //recovery完成後的最後一個everson,也就是pg處於clean狀態的最後一次操作的 eversion
  epoch_t last_epoch_started;// last epoch at which this pg started on this osd 這個pg在這個osd上的最近的的開始的epoch,也就是最近一次peering完成後的epoch
  version_t last_user_version; // last user object version applied to store
  eversion_t log_tail;     // oldest log entry.
  hobject_t last_backfill;   // objects >= this and < last_complete may be missing

  interval_set<snapid_t> purged_snaps;  //pg的要刪除的snap集合

  pg_stat_t stats;
  pg_history_t history;  //pg的歷史資訊
  pg_hit_set_history_t hit_set;  //這個是cache tie用的hit_set
  ....
  }

get_infos

generate_past_intervals

計算past intervals

build_prior

根據past inerval 計算probe_targes

  1. 首先把當前PG中的 acting 和 up 的OSD 加入到probe 列表中
  2. 檢查每個past_intervals 裡

    • 如果interval.last < info.history.last_epoch_started,這種情況下,根本就是不care
    • 如果該interval 的act為空
    • 如果該interval 沒有rw操作

    Probe: 現在 up 和acting的osd,以及在past interval 期間up的osd,用於獲取權威日誌和後續資料恢復
    up_now 儲存在該interval 內 up的osd
    down 儲存現在已經down的osd

  3. 呼叫pcontdec 函式檢查該interval的up_now 的OSD是否有足夠的OSD,對應replicated類,就是否有一正常的OSD,對應EC(n+m),是否有n個OS

  4. 檢查是否需要need_up_thru
  5. 設定probe_targets

get_infos

void PG::RecoveryState::GetInfo::get_infos()
函式get_infos 向prior_set的probe 集合中的每個osd傳送pg_query_t::INFO的訊息,來獲取pg_info資訊

context< RecoveryMachine >().send_query(
             peer, 
             pg_query_t(pg_query_t::INFO,
                        it->shard, pg->pg_whoami.shard,
                        pg->info.history,
                        pg->get_osdmap()->get_epoch())
             );
      peer_info_requested.insert(peer);
      pg->blocked_by.insert(peer.osd)

傳送訊息的過程呼叫RecoveryMachine類的send_query 函式

收到查詢peer_info的ack處理

在主osd 收到pg_info的ack時,包裝成MNotifyRec的事件傳送給狀態機。
boost::statechart::result PG::RecoveryState::GetInfo::react(const MNotifyRec& infoevt)

  1. 首先從peer_info_requested 裡刪除該peer,同時從blocked_by佇列裡刪除
  2. 呼叫函式bool PG::proc_replica_info(pg_shard_t from, const pg_info_t &oinfo)來處理副本的pg_info

    • 首先檢查該osd的pg_info是否已經儲存,並且last_update引數相同,則說明已經處理過,返回false
    • 確定自己是primary,把該osd的peer_info資訊儲存peer_info陣列,並加入might_have_unfound數組裡,該陣列的osd儲存一些物件,在後續的恢復操作中使用
    • 呼叫函式unreg_next_scrub (目前不少特別清楚)
    • 呼叫info.history.merge 函式處理slave osd發過來的pg_info資訊,基本就是更新為最新的欄位。設定dirty_info 為true
    • 呼叫 reg_next_scrub()
    • 如果該osd既不在up陣列中也不再acting陣列中,那就就是加入的stray_set列表中,如果pg處於clean狀態,就呼叫purge_strays函式刪除stray狀態的pg以及其上的物件資料
    • 如果是一個新的osd,就呼叫函式update_heartbeat_peers 更新需要heartbeat的osd列表
  3. old_start儲存了呼叫proc_replica_info前主osd的pg->info.history.last_epoch_started,如果該epoch值小於合併後的值,說明該值更新,從osd上的epoch值比較新

    • 則呼叫pg->build_prior重新構建prior_set
    • 從peer_info_requested佇列中去掉上次構建的prior_set中存在osd,這最新構建中不存在的osd
    • 呼叫get_infos函式重新發送查詢peer_info請求
  4. 呼叫pg->apply_peer_features更新features(具體什麼features,不太清楚)

  5. 當peer_info_requested佇列為空,並且prior_set不處於pg_down的狀態時,說明收到所有的osd的peer_info並處理完成
  6. 最後檢查 past_interval階段至少有一個osd處於up並且非incomplete的狀態
  7. 最後完成處理 ,呼叫函式post_event(GotInfo())丟擲GetInfo事件進入狀態機的下一個狀態。

PG_GetLog

當pg的主osd獲取各個從osd(以及past interval期間的參與的osd)的pg_info的資訊後選取一個權威的日誌的osd

PG::RecoveryState::GetLog::GetLog

  1. 呼叫函式pg->choose_acting(auth_log_shard)選取擁有權威日誌的osd,輸出儲存在auth_log_shard裡
  2. 如果auth_log_shard == pg->pg_whoami,也就是選擇的擁有權威日誌的log就是自己,直接丟擲事件GotLog()完成操作
  3. 如果自己不是,則需要去擁有權威日誌的osd上去拉取權威日誌,收到權威日誌後,觸發GetLog事件

boost::statechart::result PG::RecoveryState::GetLog::react(const GotLog&)
收到GetLog事件的處理

proc_master_log

void PG::proc_master_log
合併權威日誌到本地pg log

find_best_info

函式find_best_info完成根據各個osd的pg_info_t的資訊,選取一個擁有權威日誌的osd,選擇的優先順序:

  • Prefer newer last_update
  • Prefer longer tail if it brings another info into contiguity
  • Prefer current primary

程式碼實現具體的過程如下:

  • 首先計算min_last_update_acceptable 和 max_last_epoch_started
  • 如果min_last_update_acceptable == eversion_t::max(),也就是沒有效的min_last_update, 直接返回infos.end()的空iterator (這種情況下,pg可能處於初始化或者pg沒有寫操作)
  • 根據以下條件選擇一個osd
    • 首先把last_update小於min_last_update_acceptable或者last_epoch_started小於 max_last_epoch_started_found 或者處於incomplete的osd去掉
    • 如是ec,選擇最新的last_update, 如果是replicate,選擇最大的last_update的osd
    • 選擇longer tail和 current primary

calc_replicated_acting

  1. 首先選擇primay osd. 如果up_primary 處於非incomplete狀態,並且last_update >= auth_log_shard->second.log_tail, 那麼優先選擇up_pirmray為primray,否則選擇auth_log_shard為primray
  2. 以下過程選擇可用的size個和 primay完全,分別新增到佇列:
    backfill:需要backfill的osd集合
    acting_backfill: backfill + acting
    want: 和acting_backfill 集合相同,主要是在compat_mode模式下相容Erasure Code
    • 首先在up裡面選擇,如果處於incomplete的狀態, cur_info.last_update小於primary和 auth_log_shard的最小值,該pg 無法根據pg log 來進行部分物件的的拷貝修復,需要backfill,把該osd分別加入backfill 和 acting_backfill集合,否則只加入acting_backfill集合
    • 同樣的流程,在acting 和 all_info 中選擇

choose_acting

  1. 首先呼叫find_best_info函式獲取擁有權威日誌的osd
  2. 如果沒有選舉出權威日誌的osd,如何處理 ?目前這個邏輯不是特別清楚
  3. 如果當前的primary
  4. 計算是否是compat_mode
  5. 呼叫calc_replicated_acting,計算出backfill 和 acting_backfill(want)集合
  6. 計算num_want_acting數量,檢查如果小於min_size,對於EC處於incomplete狀態,對於Replicae 處於Peered狀態,並返回flase
  7. 檢查是否有足夠的osd來進行recovery
  8. 檢查如果acting不等於up,需要申請pg_temp
  9. 最後驗證,如果backfill_targets為空,strary_set裡面應該沒有want_backfill的osd;否則如果backfill_targets不為空,需要backfill的osd不在strary_set中

PG_GetMissing

PG::RecoveryState::GetMissing::GetMissing(my_context ctx)

  1. 遍歷pg->actingbackfill集合的osd
  2. 如果osd為backfill或者日誌完整,只新增到pg->peer_missing
  3. 如果日誌不全,向該osd傳送請求,獲取log+missing 或者 fulllog+missing

boost::statechart::result PG::RecoveryState::GetMissing::react(const MLogRec& logevt)
當收到獲取日誌的ack應對,包裝成MLogRec事件,GetMissing狀態處理該事件

  1. 呼叫proc_replica_log處理日誌
  2. 如果peer_missing_requested為空,即所有的獲取日誌的請求返回並處理,如果需要pg->need_up_thru,丟擲 post_event(NeedUpThru()); 否則,直接 post_event(Activate(pg->get_osdmap()->get_epoch())); 進入Activate狀態

proc_replica_log

  1. 呼叫pg_log.proc_replica_log來處理日誌,輸出為omissing,也就是該osd缺失的物件
  2. 加入might_have_unfound.insert(from);
  3. 加入peer_missing[from].swap(omissing)

Activate

PG::RecoveryState::Active::Active(my_context ctx)

  1. 在建構函式裡初始化了remote_shards_to_reserve_recovery 和remote_shards_to_reserve_backfill,需要recovery和backfill的
  2. pg->start_flush 這個目前不清楚幹啥用的
  3. 呼叫pg->activate

Active

PG::activate 這個函式是peering過程的最後一步。 這個函式在ReplicaActive中啟用slave osd時也呼叫本函式,所有裡面有分別對primay osd和 slave osd 的兩種情況的分別處理。

  1. 如果有replay,處理replay
  2. 如當前osd是primary, 並且acting.size() >= pool.info.min_size時更新info.last_epoch_started;如果是slave osd,確保該osd是acting狀態,更新info.last_epoch_started。
  3. 更新一些欄位

  4. find out when we commit

  5. initialize snap_trimq
  6. 處理complete pointer
    如果 missing.num_missing() == 0,表明沒有miss的object,處於clean 狀態。直接更新info.last_complete = info.last_update,並呼叫pg_log.reset_recovery_pointers() 調整指標
    否則pg_log.activate_not_complete(info)
  7. 以下都是primay的操作
    • pi.last_update == info.last_update
    • 否則需要backfill
    • Set up missing_loc
    • needs_recovery,把該pg加入到 osd->queue_for_recovery(this);
    • 標記pg的狀態為 PG_STATE_ACTIVATING

AllReplicasActivated

boost::statechart::result PG::RecoveryState::Active::react(const AllReplicasActivated &evt)
當所有的replica 處於activated 狀態時:

  1. 取消PG_STATE_ACTIVATING和PG_STATE_CREATING狀態, 如果該pg的acting的osd的數量大於等於pool的min_size,設定該pg為PG_STATE_ACTIVE的狀態,否則設定為PG_STATE_PEERED狀
  2. share_pg_info
  3. ReplicatedPG::check_local,檢查本地的stray物件都被刪除了
  4. 如果有讀寫請求在等待peering完成,則把該請求新增到處理佇列 pg->requeue_ops(pg->waiting_for_peered);
  5. 呼叫void ReplicatedPG::on_activate
  6. 如果需要recovery, 觸發DoRecovery事件,如果需要backfill, 觸發RequestBackfill事件,否則觸發AllReplicasRecovered事件
  7. 初始化Cache Tier 需要的hist_set, hit_set_setup
  8. 初始化 Cache Tier需要的agent,agent_setup

相關推薦

ceph 原始碼分析peering 過程

本人最近仔細研究ceph 恢復部分的原始碼,這個閱讀分析的過程比較艱難,分享在此,希望大家能互相交流學習,有不正確的地方,希望大家指正! Peering的作用 Peer的過程,是使一個PG內的OSD達成一個一致狀態,當主從副本完成達成一個一致的狀態,pe

從Caffe原始碼分析訓練過程

下面開始來具體進行介紹。 先從Caffe.cpp檔案中的train()函式開始說起。 1、建立一個SolverParameter solver_param用來儲存求解(優化)的一些引數,SolverParameter這個資料結構具體被定義在caffe.proto檔案中。 2、Caffe::readP

ceph原始碼分析--ceph命令解析(終端敲命令之後發生的事)

在一次開發組內討論中,那次是admin_socket的後臺部分的課程分享。分享中同事提出了一個疑問。既然講了admin_socket的的後臺部分,那前端輸入命令到底是怎麼去呼叫的後臺呢,或者前臺的命令到底是怎麼傳送的呢?平時的ceph命令到底是怎麼解析的呢?討論

另闢蹊徑Ceph原始碼分析之3:解析ceph pg_temp(ceph 臨時pg)

什麼是pg_temp 假設一個PG通過crush演算法對映到的三個osd是[0,1,2],此時,如果osd0出現故障,導致crush演算法重新分配該PG的三個osd是[3,1,2],此時,osd3為該PG的主osd,但是osd3為新加入的osd,並不

小夥伴們的ceph原始碼分析二——monitor啟動流程

程式碼位置:src/ceph_mon.cc 首先help看下ceph-mon的usage usage: ceph-mon -i monid [--mon-data=pathtodata][flags] --debug_mon n     debug monitor l

ceph原始碼分析--一個entry函式的呼叫

ceph中有很多的entry函式,往往是由各執行緒來呼叫處理的,本篇文章以bluestore中的MempoolThread為例來講講該函式的呼叫。 struct MempoolThread : public Thread { BlueStore *s

小夥伴們的ceph原始碼分析三——monitor訊息處理流程

筆者在讀程式碼初期非常想理清楚的就是ceph這麼個系統在服務端與客戶端是怎麼響應與發起請求的。 本人主要負責monitor部分,而且追了一會cephx認證的程式碼,所以拿這塊舉例,後續osd部分主要是對同事分享的學習。 本篇會講到src/mon/monitor.cc中 c

Ceph 原始碼分析 -OS module

Ceph的src/os 中儲存了 ObjectStore程式碼實現。 基本概念 物件 物件儲存的概念,把物件作為儲存的實體。 在Ceph 檔案系統裡,物件的名稱空間分了兩層, 第一層是Collection的概念,一個Collection就是許多Object的集合,相當於

記一次Ceph日誌損壞的分析處理過程

Ceph 日誌 1、故障現象 今天下午看到群友在說一個問題,說ceph的某個osd處於down的狀態,我大概整理下他的處理過程 1、查看OSD的狀態2、查看日誌信息3、啟動對應的ceph-osd服務4、檢查集群健康狀態 2、日誌損壞了,如何讓osd重新上線 思路:重建日誌a、先把/var/lib/ce

Netty NioEventLoop 啟動過程原始碼分析

原文連結:https://wangwei.one/posts/netty-nioeventloop-analyse-for-startup.html 前面 ,我們分析了NioEventLoop的建立過程,接下來我們開始分析NioEventLoop的啟動和執行邏輯。

Netty NioEventLoop 建立過程原始碼分析

原文:https://wangwei.one/posts/netty-nioeventloop-analyse-for-create.html 前面 ,我們分析了Netty中的Channel元件,本篇我們來介紹一下與Channel關聯的另一個核心的元件 —— EventLo

Netty(五)服務端啟動過程原始碼分析——好文摘抄

下面先來一段 Netty 服務端的程式碼: public class NettyServer { public void bind(int port){ // 建立EventLoopGroup EventLoopGroup bossGroup = new

自定義spring boot starter三部曲之三:原始碼分析spring.factories載入過程

本文是《自定義spring boot starter三部曲》系列的終篇,前文中我們開發了一個starter並做了驗證,發現關鍵點在於spring.factories的自動載入能力,讓應用只要依賴starter的jar包即可,今天我們來分析Spring和Spring boot原始碼,瞭解s

lucene原始碼分析(2)讀取過程例項

1.官方提供的程式碼demo Analyzer analyzer = new StandardAnalyzer(); // Store the index in memory: Directory directory = new RAMDirec

Tinyxml解析過程原始碼分析

        tinyxml是一個優秀的,易用的,開源的xml解析庫,xml解析的最關鍵之處,就是如何將xml檔案內容解析成記憶體中的可用、易用的程式資料---DOM(Document Object Model)樹。DOM其實就是多叉樹,每個節

Android原始碼分析-Activity的啟動過程

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

go原始碼分析(一) 通過除錯看go程式初始化過程

參考資料:Go 1.5 原始碼剖析 (書籤版).pdf 編寫go語言test.go package main import ( "fmt" ) func main(){ fmt.Println("Hello World") }  帶除錯的編譯程式碼 go build -

ceph thread原始碼分析

簡介 ceph中的很多工都是用thread實現的,比如說adminsocket, log, timer等.可以說,thread是ceph中最基本的模組,因此我們的ceph原始碼分析從thread開始.本篇主要介紹三部分內容: thread mutex condit

Uboot啟動過程原始碼分析之第二階段

UBoot的最終目標是啟動核心 1.從Flash中讀出核心 2.啟動核心 通過呼叫lib_arm/board.c中的start_armboot函式進入uboot第二階段 第二階段總結圖 typedef struct global_data { bd_t *bd; unsigned

Uboot啟動過程原始碼分析之第一階段(硬體相關)

從上一個部落格知道uboot的入口點在 cpu/arm920t/start.s 開啟cpu/arm920t/start.s 跳轉到reset reset: /* * set the cpu to SVC32 mode// CUP設定為管理模式 */ mrs r0,cps