集羣中的設備異常(異常OSD的添加刪除操做),會致使PG的各個副本間出現數據的不一致現象,這時就須要進行數據的恢復,讓全部的副本都達到一致的狀態。html
1. OSD的故障種類:react
故障A:一個正常的OSD 由於所在的設備發生異常,致使OSD不能正常工做,這樣OSD超過設定的時間 就會被 out出集羣。算法
故障B: 一個正常的OSD由於所在的設備發生異常,致使OSD不能正常工做,可是在設定的時間內,它又能夠正常的工做,這時會添加會集羣中。數組
2. OSD的故障處理:app
故障A:OSD上全部的PG,這些PG就會從新分配副本到其餘OSD上。一個PG中包含的object數量是不限制的,這時會將PG中全部的object進行復制,可能會產生很大的數據複製。函數
故障B:OSD又從新回到PG當中去,這時須要判斷一下,若是OSD可以進行增量恢復則進行增量恢復,不然進行全量恢復。(增量恢復:是指恢復OSD出現異常的期間,PG內發生變化的object。全量恢復:是指將PG內的所有object進行恢復,方法同故障A的處理)。post
須要全量恢復的操做叫作backfill操做。須要增量恢復的操做叫作recovery操做。ui
2、概念解析:spa
1.osdmap:集羣全部osd的集合,包括每一個osd的ip & state(up or down).net
2.acting set & up set:每一個pg都有這兩個集合,acting set中保存是該pg全部的副本所在OSD的集合,好比acting[0,1,2],就表示這個pg的副本保存在OSD.0 、OSD.一、OSD.2中,並且排在第一位的是OSD.0 ,表示這個OSD.0是PG的primary副本。在一般狀況下 up set 與 acting set是相同的。區別不一樣之處須要先了解pg_temp。
3.Epoch:osdmap的版本號,單調遞增,osdmap每變化一次加1
4.current_interval & past interval:一個epoch序列,在這個序列內,這個PG的acting set沒有變化過,current是當前的序列,past是指過去的interval。
last_epoch_started:上次通過peering後的osdmap版本號epoch。
last_epoch_clean:上次通過recovery或者backfill後的osdmap版本號epoch。
(注:peering結束後,數據的恢復操做纔剛開始,因此last_epoch_started與last_epoch_clean可能存在不一樣)。
例如:
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
5.authoritative history:完整的pg log操做序列
6.last epoch start:上次peering完成的epoch
7.up_thru:一個past interval內,第一次完成peering的epoch
8.pg_temp : 假設當一個PG的副本數量不夠時,這時的副本狀況爲acting/up = [1,2]/[1,2]。這時添加一個OSD.3做爲PG的副本。通過crush的計算髮現,這個OSD.3應該爲當前PG的primary,可是呢,這OSD.3上面尚未PG的數據,因此沒法承擔primary,因此須要申請一個pg_temp,這個pg_temp就還採用OSD.1做爲primary,此時pg的集合爲acting,pg_temp的集合爲up。固然pg與pg_temp是不同的,因此這時pg的集合變成了[3,1,2]/[1,2,3]。當OSD.3上的數據所有都恢復完成後,就變成了[3,1,2]/[3,1,2]。
9.pg_log:pg_log是用於恢復數據重要的結構,每一個pg都有本身的log。對於pg的每個object操做都記錄在pg當中。
__s32 op; 操做的類型
hobject_t soid; 操做的對象
eversion_t version, prior_version, reverting_to; 操做的版本
3、peering具體流程
算法流程圖:
Peering:互爲副本的三個(此處爲設置的副本個數,一般設置爲3)pg的元數據達到一致的過程。官方解釋以下:
the process of bringing all of the OSDs that store a Placement Group (PG) into agreement about the state of all of the objects (and their metadata) in that PG. Note that agreeing on the state does not mean that they all have the latest contents.
primary PG和raplica PG: 互爲副本的三個pg中,有一個主,另外兩個爲輔;其中爲主的稱爲primary PG,其餘兩個都稱爲replica PG。
故障osd從新上線後,primary PG和replica PG會進入不一樣的處理流程。primary PG會先進入peering狀態,在這個狀態的pg暫停處理IO請求,在生產環境中表現爲集羣部分IO不響應,甚至某些雲主機由於等待IO形成應用沒法正常處理。下面就peering過程的主要操做結合源碼進行分析。
pg是由boost::statechart實現的狀態機,peering經歷如下主要過程:
一、GetInfo:
1.一、選取一個epoch區間,對區間內的每一個epoch計算其對應的acting set、acting primary、up set、up primary,將相同的結果做爲一個interval;
pg->generate_past_intervals();
調用generate_past_intervals()函數,生成past_interval序列。首先肯定查找interval的start_epoch(history.last_epoch_clean 上次恢復數據完成的epoch)和end_epoch(history.same_interval_since 最近一次interval的起始epoch)。肯定了start_epoch和end_epoch以後,循環這個兩個版本間的全部osdmap,肯定pg成員變化的區間interval。
1.二、判斷每一個interval,將up狀態的osd加入到prior set;同時將當前的acting set和up set加入到prior set;
pg->build_prior(prior_set);
據past_interval生成prior set集合。肯定prior set集合,若是處於當前的acting和up集合中的成員,循環遍歷past_interval中的每個interval,interval.last >= info.history.last_epoch_started、! interval.acting.empty()、interval.maybe_went_rw,在該interval中的acting集合中,而且在集羣中仍然是up狀態的。
1.三、向prior_set中的每一個up狀態的osd發送Query INFO請求,並等待接收應答,將接收到的請求保存到peer_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()));
根據priorset 集合,開始獲取集合中的全部osd的info。這裏會向全部的osd發送請求info的req(PG::RecoveryState::GetInfo::get_infos())。發送請求後等待回覆。
1.四、收到最後一個應答後,狀態機post event到GotInfo狀態;若是在此期間有一個接收請求的osd down掉,這個PG的狀態將持續等待,直到對應的osd恢復;
boost::statechart::result PG::RecoveryState::GetInfo::react(const MNotifyRec &infoevt)
回覆處理函數。主要調用了pg->proc_replica_info進行處理:1.將info放入peerinfo數組中。2.合併history記錄。 在這裏會等待全部的副本都回復info信息。進入下一個狀態GetLog。
二、GetLog:
2.一、遍歷peer_info,查找best info,將其做爲authoritative log;將acting set/peer_info中將處於complete狀態的pg以及up set的全部pg存入acting_backfill;
pg->choose_acting(auth_log_shard,
&context< Peering >().history_les_bound)
經過pg->choose_acting(auth_log_shard)選擇acting集合和auth_osd.
choose_acting中主要進行了兩項重要的措施:
find_best_info,查找一個最優的osd。在 find_best_info中查找最優的osd時,判斷的條件的優先級有三個:最大的last_update、最小的log_tail、當前的primary。
map<pg_shard_t, pg_info_t>::const_iterator auth_log_shard =
find_best_info(all_info, history_les_bound);calc_replicated_acting ,選擇參與peering、recovering的osd集合。
up集合中的成員。全部的成員都是加入到acting_backfilling中,若是是incomplete狀態的成員或者 日誌銜接不上的成員(cur.last_update<auth.log_tail)則添加到backfill中,不然添加到want成員中。
acting集合中的成員,該組內的成員不會添加到backfill中,因此只須要判斷 若是狀態是complete而且 日誌可以銜接的上,則添加到want和acting_backfilling中。
其餘prior中的osd成員 處理同acting一致。
通過這一步可知,acting_backfilling的成員(可用日誌恢復數據,或者幫助恢復數據的成員),backfill的成員(只能經過其餘的osd上pg的數據進行全量拷貝恢復),want的成員(一樣在acting_backfill中,可是不一樣於backfill的成員)。
calc_ec_acting。ceph有兩種pool,一種是副本類型pool,一種是糾刪碼類型pool(相似RAID)。具體實現後續補充,今天太晚了,有空看代碼補補。
2.二、若是計算出的authoritative log對應的pg是自身,直接post event到GotLog;不然,向其所在的osd發送Query Log請求;
context<RecoveryMachine>().send_query(
auth_log_shard,
pg_query_t(
pg_query_t::LOG,
auth_log_shard.shard, pg->pg_whoami.shard,
request_log_from, pg->info.history,
pg->get_osdmap()->get_epoch()));
2.三、接收請求的osd應答,並將獲取的log merge到本地,狀態機post event到GetMissing;若是收不到應答,狀態將持續等待;
boost::statechart::result PG::RecoveryState::GetLog::react(const GotLog &)
{
dout(10) << "leaving GetLog" << dendl;
PG *pg = context< RecoveryMachine >().pg;
if (msg)
{
dout(10) << "processing master log" << dendl;
pg->proc_master_log(*context<RecoveryMachine>().get_cur_transaction(),
msg->info, msg->log, msg->missing,
auth_log_shard);//log處理函數
}
pg->start_flush(
context< RecoveryMachine >().get_cur_transaction(),
context< RecoveryMachine >().get_on_applied_context_list(),
context< RecoveryMachine >().get_on_safe_context_list());
return transit< GetMissing >();//跳轉到GetMissing
}
void PG::proc_master_log(
ObjectStore::Transaction &t, pg_info_t &oinfo,
pg_log_t &olog, pg_missing_t &omissing, pg_shard_t from)
{
dout(10) << "proc_master_log for osd." << from << ": "
<< olog << " " << omissing << dendl;
assert(!is_peered() && is_primary());// merge log into our own log to build master log. no need to
// make any adjustments to their missing map; we are taking their
// log to be authoritative (i.e., their entries are by definitely
// non-divergent).
merge_log(t, oinfo, olog, from);//該函數對log進行合併,造成一個權威順序完整的一個log。包括日誌先後的修補,並且最重要的是修補的過程當中,統計了本地副本中須要恢復object的狀況missing.add_next_event(ne)。這裏已經開始統計missing結構了。
peer_info[from] = oinfo;//保存來自best_log的oinfo到本地的peer-info數組中。
dout(10) << " peer osd." << from << " now " << oinfo << " " << omissing << dendl;
might_have_unfound.insert(from);// See doc/dev/osd_internals/last_epoch_started
if (oinfo.last_epoch_started > info.last_epoch_started)
{
info.last_epoch_started = oinfo.last_epoch_started;
dirty_info = true;
}
if (info.history.merge(oinfo.history)) //對history信息進行合併。
dirty_info = true;
assert(cct->_conf->osd_find_best_info_ignore_history_les ||
info.last_epoch_started >= info.history.last_epoch_started);peer_missing[from].swap(omissing);//將missing結構統計到本地的peer_missing結構中。
}
auth_log:一個是auth_log的合併,最大最權威的log,恢復數據要根據這裏進行。
missing:另外就是合併log過程當中發現本地副本須要恢復的object集合。
omissing:auth_osd須要進行恢復的object集合。
三、GetMissing:
3.一、遍歷acting_backfill,向與primary pg log有交集的pg所在的osd發送Query Log請求;將剩餘沒有交集的pg放入peer_missing,生成missing set用於後續recovery;
context< RecoveryMachine >().send_query(
*i,
pg_query_t(
pg_query_t::LOG,
i->shard, pg->pg_whoami.shard,
since, pg->info.history,
pg->get_osdmap()->get_epoch()));
3.二、將收到的每個應答merge到本地,若是在此期間有osd down掉,這個PG的狀態將持續等待;收到全部的應答後,當前pg的狀態機進入Activate狀態,peering過程結束;
boost::statechart::result PG::RecoveryState::GetMissing::react(const MLogRec &logevt)
{
PG *pg = context< RecoveryMachine >().pg;peer_missing_requested.erase(logevt.from);
pg->proc_replica_log(*context<RecoveryMachine>().get_cur_transaction(),
logevt.msg->info, logevt.msg->log, logevt.msg->missing, logevt.from);//接收到其餘osd發回的log信息而且進行處理。在proc_replica_log中對peer_log進行修剪,丟棄那些不完整不可用的log。整理接收到的oinfo到peerinfo中,omissing到peer_missing中。直接來到active狀態。if (peer_missing_requested.empty())
{
if (pg->need_up_thru)
{
dout(10) << " still need up_thru update before going active" << dendl;
post_event(NeedUpThru());
}
else
{
dout(10) << "Got last missing, don't need missing "
<< "posting Activate" << dendl;
post_event(Activate(pg->get_osdmap()->get_epoch()));
}
}
return discard_event();
}
從以上分析來看,整個peering過程主要分爲三個階段,GetInfo -> GetLog -> GetMissing,首先向prior set、acting set、up set中的每一個osd請求pg infos, 選出authoritative log對應的pg;其次向authoritative log所在的osd請求authoritative log;最後獲取recovery過程須要的missing set;
peering時間的長短並不可控,主要是在於請求的osd是否可以及時響應;若是這個階段某個osd down掉,極可能致使部分pg一直處在peering狀態,即全部分佈到這個pg上的IO都會阻塞。
此文僅講述了peering過程,peering以後還會進行recovery操做,recovery操做由處理線程直接調用函數void OSD::do_recovery(PG *pg, ThreadPool::TPHandle &handle)進行,後續再總結總結recovery過程和PG的狀態機。
先附兩張PG狀態機的類型以及流程圖:
參考資料:
做者:一隻小江 http://my.oschina.net/u/2460844/blog/596895
做者:王鬆波 https://www.ustack.com/blog/ceph%EF%BC%8Dpg-peering/
做者:劉世民(Sammy Liu) http://www.cnblogs.com/sammyliu/p/4836014.html
做者:常濤 http://blog.csdn.net/changtao381/article/details/49125817
感謝以上做者無私的分享。