集群中的設備異常(異常OSD的添加刪除操作),會導致PG的各個副本間出現數據的不一致現象,這時就需要進行數據的恢復,讓所有的副本都達到一致的狀態。
一、OSD的故障和處理辦法:
1. OSD的故障種類:
故障A:一個正常的OSD 因為所在的設備發生異常,導致OSD不能正常工作,這樣OSD超過設定的時間 就會被 out出集群。
故障B: 一個正常的OSD因為所在的設備發生異常,導致OSD不能正常工作,但是在設定的時間內,它又可以正常的工作,這時會添加會集群中。
2. OSD的故障處理:
故障A:OSD上所有的PG,這些PG就會重新分配副本到其他OSD上。一個PG中包含的object數量是不限制的,這時會將PG中所有的object進行復制,可能會產生很大的數據復制。
故障B:OSD又重新回到PG當中去,這時需要判斷一下,如果OSD能夠進行增量恢復則進行增量恢復,否則進行全量恢復。(增量恢復:是指恢復OSD出現異常的期間,PG內發生變化的object。全量恢復:是指將PG內的全部object進行恢復,方法同故障A的處理)。
需要全量恢復的操作叫做backfill操作。需要增量恢復的操作叫做recovery操作。
二、概念解析:
1.osdmap:集群所有osd的集合,包括每個osd的ip & state(up or down)
2.acting set & up set:每個pg都有這兩個集合,acting set中保存是該pg所有的副本所在OSD的集合,比如acting[0,1,2],就表示這個pg的副本保存在OSD.0 、OSD.1、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; 操作的版本
三、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。
1、peering過程的影響
故障osd重新上線后,primary PG和replica PG會進入不同的處理流程。primary PG會先進入peering狀態,在這個狀態的pg暫停處理IO請求,在生產環境中表現為集群部分IO不響應,甚至某些雲主機因為等待IO造成應用無法正常處理。下面就peering過程的主要操作結合源碼進行分析。
2、peering過程分析
pg是由boost::statechart實現的狀態機,peering經歷以下主要過程:
1、GetInfo:
1.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.2、判斷每個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.3、向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.4、收到最后一個應答后,狀態機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。
2、GetLog:
2.1、遍歷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.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.3、接收請求的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集合。
3、GetMissing:
3.1、遍歷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.2、將收到的每一個應答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();
}
3、總結
從以上分析來看,整個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
感謝以上作者無私的分享。