Ceph集羣在系統擴容時觸發rebalance的機制分析react
1、概述。函數
Ceph系統擴容無非就是向集羣中添加新的存儲節點來擴充當前Ceph集羣的容量。當集羣中有新的存儲節點加入後,整個Ceph集羣中的OSDs節點數目就會發生變化(即:OSDMap發生變化),CRUSHMap也會發生變化(好比:新加入一個機櫃到Ceph集羣)。所以當前集羣中的部分PGs就會根據最新的CRUSHMap和OSDMap從新計算primary和replicas節點,使得新加入集羣的OSDs可以處理PGs的主副本,進而使得Ceph集羣中的數據可以均勻的分佈在整個Ceph集羣中。post
2、rebalance觸發機制分析。ui
一、OSDMap變化解析。spa
當有新的OSD節點加入到Ceph集羣時,須要從OSD的main()函數入手分析OSD是如何加入到Ceph集羣中也就是說OSD節點如何添加到OSDMap中的。Ceph源代碼調用流程以下:線程
ceph_osd.cc對象
|__OSD::init()繼承
|__OSD::start_boot()隊列
|__MonClient::get_version()向Monitor發送MMonGetVersion消息來獲取當前OSDMap的最新和最舊的版本號。調用該函數是傳入一個回調函數結構體C_OSD_GetVersion,在獲取到OSDMap最新和最舊的版本號時調用該結構體中的void finish()方法,在該方法中調用OSD::_maybe_boot()函數作進一步處理。事件
OSD::_maybe_boot()
|__OSD::_send_boot()建立MOSDBoot消息且將該消息發送給Monitor節點。
Monitor節點上負責接收和處理Message的函數是Monitor::ms_dispatch()函數,該函數獲取從其它節點上發送過來的消息。Monitor::ms_dispatch()函數處理流程以下:
Monitor::ms_dispatch()
|__Monitor::_ms_dispatch()
|__Monitor::dispatch_op()該函數是處理消息的路由函數,根據消息類型決定具體的處理函數。對於MOSDBoot消息由PaxosService::dispatch()函數作進一步處理。在該函數處理過程當中會依次調用PaxosService::preprocess_query()方法和PaxosService::prepare_update()方法,這兩個方法在PaxosService類中是虛函數。因爲Monitor節點上負責處理OSDMap的類是OSDMonitor類,該類繼承自PaxosService類,在OSDMonitor類中實現了具體處理流程。因爲OSD節點是新加入的所以當前OSDMap上沒有該節點的信息,所以OSDMonitor::preprocess_query()函數返回false,須要調用OSDMonitor::prepare_update()函數作進一步處理。
Monitor::dispatch_op()
|__PaxosService::dispatch()
|__OSDMonitor::preprocess_query()
|__OSDMonitor::prepare_update()
|__OSDMonitor::prepare_boot()在該函數中建立一個OSDMap的pending_inc結構,以後調用wait_for_finished_proposal()函數來申請一個提案,在Monitor集羣中達成一致,在該提案達成一致後回調C_Booted::_finish()函數,該函數進一步調用OSDMonitor::_booted()函數。OSDMonitor::_booted()函數調用send_lastest()函數將當前最新的OSDMap發送給新加入的OSD節點。
二、OSD節點處理OSDMap過程解析。
OSD節點處理Monitor節點發送過來的OSDMap消息的處理流程以下:
OSD::_dispatch()
|__OSD::handle_osd_map()
|__OSD::consume_map()
|__PG::queue_null()
|__PG::queue_peering_event()
OSD::_dispatch()函數是消息處理的路由函數,根據消息類型調用具體的處理函數。對於處理Monitor節點發送過來的OSDMap消息,則由handle_osd_map()函數進行處理。在handle_osd_map()函數中首先對OSDMap消息進行解析且獲得OSDMap且保存,以後調用consume_map()作進一步處理。在consume_map()函數中遍歷該OSD節點上已有的PGs且統計出primary/replicas/stray的數量,其次喚醒等待OSDMap的PGs,最後遍歷當前OSD節點上全部PGs且調用PG::queue_null()函數將OSD節點上全部PGs添加到peering隊列中。
對於新加入的OSD節點來講,因爲其上沒有PGs,所以新加入的OSD節點上並無PGs加入到peering隊列,因此新加入的OSD節點暫時沒有後續的操做。
因爲OSDMap有變化,所以Monitor節點會把最新的OSDMap發送給Ceph集羣中全部的OSDs節點,使得集羣中全部OSDs節點都拿到最新的OSDMap。根據上面的分析咱們知道當前集羣中的OSDs節點上會有PGs,所以當前集羣上有PGs的OSDs節點都會將全部PGs添加到peering隊列中,進而致使PG狀態機的變化,使得PG從peering狀態逐步轉換到active+clean狀態。
三、PG狀態機過程解析。
PG處於peering狀態時須要通過GetInfo/GetLog/GetMissing三個狀態,只有這三個狀態都能順利經過後,當前PG狀態變爲active狀態以後進行recovery和backfill最終變成active+clean狀態。若這三個狀態中任意狀態沒法正常完成則會等待並一直處於該狀態最終變成inactive狀態。
GetInfo狀態:
GetInfo::GetInfo()
|__PG::generate_past_interval()
|__PG::build_prior()
|__GetInfo::get_infos()
|__GetInfo::react(const MNotifyRec&)
|__PG::proc_replica_info()
|__post_event(GotInfo())
PG::generate_past_interval():生成past_interval結構:past_interval中保存PG副本發生變化時,從上次PG穩定的epoch開始到PG副本發生變化前的epoch的集合。在past_interval中start_epoch到end_epoch過程當中該PG的副本所在的OSDs沒有發生變化;
PG::build_prior():生成past_interval結構:past_interval中保存PG副本發生變化時,從上次PG穩定的epoch開始到PG副本發生變化前的epoch的集合。在past_interval中start_epoch到end_epoch過程當中該PG的副本所在的OSDs沒有發生變化;
GetInfo::get_infos():向prior_set中的OSD節點發送獲取Info信息;
GetInfo::react(const MNotifyRec&):處理OSD節點回復的Info信息,在該函數中更新peer_info結構。當prior_set集合中全部OSD節點都回復Info消息後,發送GotInfo消息,以後進入到GetLog狀態;
GetLog狀態:
GetLog::GetLog()
|__PG::choose_acting()
|__PG::find_best_info()
|__PG::calc_replicated_acting()
|__context<RecoveryMachine>().send_query()
|__GetLog::react(const GotLog&)
|__PG::proc_master_log()
|__merge_log()
|__PGLog::merge_log()
|__更新peer_info和peer_missing結構
|__transit<GetMissing>()
PG::find_best_info():找出最具權威的OSD節點。在全部的peer_info以及本pg_info中找到最具權威的OSD。最具權威OSD節點的原則是last_update最新或者log tail最長或者當前primary OSD;
PG::calc_replicated_acting():找到合適的acting集合。acting集合包括:want/acting_backfill/backfill三個集合。
A)want:最具權威OSD節點/全部up且信息完整的OSD節點/全部acting且信息完整的OSD節點/全部peer_info且信息完整的OSD節點(want_acting)
B)acting_backfill:最具權威OSD節點/全部up且信息完整的OSD節點/全部acting且信息完整的OSD節點/全部peer_info且信息完整的OSD節點(actingbackfill)
C)backfill:up但信息不完整的OSD節點(backfill_targets)
context<RecoveryMachine>().send_query():向最具權威的OSD節點發送獲取Log信息;
PG::proc_master_log():處理權威OSD節點發送回來的Log信息:最具權威OSD節點發送過來的Log消息包括info/log/missing信息,以後調用merge_log()函數將最具權威OSD節點的log信息合併到本地且找到本PG缺失的pg_log(保存到pg_log->missing中),另外有一部分本地PG缺失的objects保存到pg_log->divergent_priors中。更新最具權威OSD節點的Info信息,即更新peer_info結構;合併history信息;更新權威OSD節點的peer_missing結構;
transit<GetMissing>():進入到GetMissing狀態;
GetMissing狀態:
GetMissing::GetMissing()
|__context<RecoveryMachine>().send_query()
|__GetMissing::react(const MLogRec&)
|__PG::proc_replica_log()
|__PGLog::proc_replica_log()
|__更新peer_info和peer_missing結構
|__post_event(Activate())
GetMissing():在actingbackfill集合中找到有效的節點(peer_info.last_update > pg_log.tail && peer_info.last_backfill != hobject_t && peer_info.last_update != pg->info.last_update)且向actingbackfill中有效的OSDs發送獲取log+missing信息(LOG或FULLLOG);
GetMissing::react(const MLogRec&):接收log+missing信息的處理函數,處理其它OSDs發送過來的log+missing信息,以後修剪oinfo以及omssing信息,丟棄那些不完整不可用的log。整理接收到的oinfo到peer_info中,omissing到peer_missing中;
post_event(Activate()):發送Activate事件到PG狀態機,Peering狀態收到Activate事件後遷移到Active狀態;
Active狀態:
Active::Active()
|__PG::Activate()
|__遍歷pg->actingbackfill將全部replicas節點插入到pg->blocked_by集合
PG::Activate()處理流程以下:
1)判斷是否須要進行replay操做。執行replay操做的緣由是在past_interval中有OSDs節點出現Crashed的狀況(比較past_interval中的參數和past_interval.acting[]中的osd_info結構中的參數),不然不會執行replay。執行replay操做時,將當前PG插入到OSD節點上的replay_queue隊列中。replay操做由OSD節點上的tick()定時器函數觸發,在tick()函數中調用OSD::check_replay_queue()函數,該函數從replay_queue隊列中取出待執行replay操做的PG,以後調用PG::replay_queued_ops()函數,將該PG插入到OSD->op_wq隊列中;
2)更新當前PG info.last_epoch_started爲當前osdmap的epoch值;
3)註冊Activate完成回調函數,在回調函數中若不是primary則向primary發送Info消息通知primary此replica完成Activate,如果primary則判斷是否全部的replicas都回復消息,如果則發送AllReplicasActivated消息;
4)對於本地有missing信息則更新pg_log->complete_to字段;
如下都是primary PG的處理。
5)遍歷actingbackfill列表,建立MOSDPGLOG消息,以peer_info信息、pg_log信息以及past_interval信息填充MOSDPGLOG消息,最終將MOSDPGLOG消息發送給actingbackfill列表中指定的OSD。在指定的OSD上執行merge_log()操做便可;
6)遍歷actingbackfill列表,對於primary來講將pg_log.missing填充到missing_loc.needs_recovery_map結構,對於replicas來講將peer_missing[]填充到missing_loc.needs_recovery_map結構;
7)對於須要進行recovery操做來講(判斷標準是primary/replicas存在missing的對象),遍歷missing_loc.needs_recovery_map中須要recovery的對象,檢查該對象是否在primary/actingbackfill/peer_missing上能被找到,若能被找到則更新missing_loc結構保存須要recovery的對象和該對象所在OSD節點的映射關係。設置當前PG的狀態爲PG_STATE_DEGRADED;
8)調用osd->queue_for_recovery()進入recovery狀態;
9)設置當前狀態爲PG_STATE_ACTIVATING;
Recovery狀態:
OSD::do_recovery()
|__檢查執行recovery的條件是否知足
|__ReplicatedPG::start_recovery_ops()
|__ReplicatedPG::recover_replicas()
|__ReplicatedPG::recover_primary()
|__ReplicatedPG::recover_replicas()
|__ReplicatedPG::recover_backfill()
|__根據當前PG不一樣的狀態,發送RequestBackfill()/AllReplicasRecovered()/Backfilled()不一樣事件
|__PG::discover_all_missing() if recovery操做已經完成可是還有missing對象
OSD::do_recovery():執行recovery操做的線程是OSD::do_recovery()函數,該函數從recovery隊列中獲取待執行recovery操做的PG,以後根據用戶設置的recovery限制條件判斷是否執行recovery操做。對於須要執行recovery操做來講,首先調用ReplicatedPG::start_recovery_ops()來執行實際的reovery操做,當recovery操做執行完成後,若後續沒有recovery操做且當前仍然還有missing對象沒有,則調用PG::discover_all_missing()找到這些missing的對象且經過獲取FULLLOG的方式從新尋找這些missing的對象;
ReplicatedPG::start_recovery_ops():該函數執行實際的recovery操做。
1)調用ReplicatedPG::recover_replicas()遍歷actingbackfill集合,將actingbackfill集合中missing的對象且primary上已經存在的對象push到actingbackfill集合對應的節點上;
2)調用ReplicatedPG::recover_primary()遍歷本地missing的對象且missing_loc中存在該對象和所在OSD的映射關係,則從missing_loc中指定的OSD節點pull missing的對象;
3)調用ReplicatedPG::recover_replicas()遍歷actingbackfill集合,將actingbackfill集合中missing的對象且primary上已經存在的對象push到actingbackfill集合對應的節點上;
4)對於須要當即執行backfill操做來講,調用ReplicatedPG::recover_backfill()執行backfill操做;
A)根據last_backfill_started和peer_info[].last_backfill,初始化backfill_info和peer_backfill_info結構。其中backfill_info保存primary中須要進行backfil的對象信息,peer_backfill_info保存peers中須要進行backfill的對象信息;
B)設置backfill_info.begin字段爲last_backfill_started,即:最近一次backfill的開始時間;
C)調用ReplicatedPG::update_range()函數更新待執行backfill操做的對象集合。
a)對於BackfillInterval->version < pg_info_t->log_tail來講調用scan_range()函數從BackfillInterval->begin開始讀取指定數量的Objects到BackfillInterval->objects結構中,該結構保存待backfill對象及其版本號的映射關係;
b)對於BackfillInterval->version > pg_info_t->log_tail來講,從pg_log中的pg_log.get_log().log->version等於BackfillInterval->version開始一直到pg_log.get_log()末尾,對該pg_log範圍內的對象來講若該對象在BackfillInterval->begin和BackfillInterval->end之間的則將該對象及其版本號寫入到BackfillInterval->objects中,不然將該對象從BackfillInterval->objects中刪除;
c)設置BackfillInterval->version = pg_info_t.last_update;
D)遍歷backfill_targets集合,調用peer_backfill_info[osd].trim_to()函數將peer_backfill_info[osd].objects中小於last_backfill_started或peer_info[osd].last_backfill的BackfillInterval從peer_backfill_info[osd].objects中刪除;
E)調用backfill_info.trim_to()函數將backfill_info.objects中小於last_backfill_started的BackfillInterval從backfill_info.objects中刪除;
F)遍歷backfill_targets集合,獲取對應peer_backfill_info[osd]信息,對於peer_backfill_info[osd].begin小於backfill_info.begin的OSD,向該OSD發送MOSDPGScan::OP_SCAN_GET_DIGEST消息。replicas節點上ReplicatedPG::do_scan()函數處理該請求,do_scan()函數讀取從m->begin開始的指定數量的對象且這些對象的範圍信息以及對象ID和版本信息經過MOSDPGScan::OP_SCAN_DIGEST發送回primary。primary節點的ReplicatedPG::do_scan()函數處理MOSDPGScan::OP_SCAN_DIGEST消息,在該處理函數中更新peer_backfill_info[osd]中對應的begin/end/objects;
G)調用earliest_peer_backfill()函數獲取到peer_backfill_info中最早須要backfill的對象。若peer_backfill_info中最早須要backfill的對象<backfill_info.begin則遍歷backfill_targets找到全部peer_backfill_info[].begin == peer_backfill中最早須要backfill的對象,以後將該對象添加到to_remove集合中且從peer_backfill_info[]中刪除該對象。若peer_backfill中最早須要backfill的OSD>=backfill_info.begin則遍歷backfill_targets獲取peer_backfill_info[]信息,對於backfill_info.begin == peer_backfill_info[].begin且backfill_info中保存的第一個待backfill操做的對象和peer_backfill_info[]中保存的第一個對象不一樣則將backfill_targets中對應的值插入到need_ver_targs中,不然插入到keep_ver_tags中。對於backfill_info.begin != peer_backfill_info[].begin,若peer_info[].last_backfill < backfill_info.begin,則將backfill_targets中對應的值插入到missing_targs中,不然插入到skip_targs中;
H)對於need_ver_targs或missing_targs不爲空,則將need_ver_targs和missing_targs中的內容寫到to_push集合中;
I)更新last_backfill_started = backfill_info.begin;
J)刪除need_ver_targs和keep_ver_targs中對應的peer_backfill_info[]項;
K)遍歷to_remove集合,調用send_remove_op()函數向指定的OSD發送MOSDSubOp且op=CEPH_OSD_OP_DELETE,使得指定的OSD節點刪除指定的對象;
L)遍歷to_push集合,調用prep_backfill_object_push()函數向指定的OSD發送PUSH消息,將對象信息發送給指定的OSD;
M)調用ReplicatedBackend::run_recovery_op()函數執行實際的push操做;
N)遍歷backfill_targets集合,若new_last_backfill > peer_info[].last_backfill,則更新peer_info[].last_backfill=new_last_backfill,若new_last_backfill == MAX則向backfill_targets對應的osd發送MOSDPGBackfill::OP_BACKFILL_FINISH消息,不然發送MOSDPGBackfill::OP_BACKFILL_PROGRESS消息,消息的last_backfill=peer_info[].last_backfill。ReplicatedPG::do_backfill()函數處理MOSDPGBackfill消息,當收到OP_BACKFILL_FINISH時發送RecoveryDone事件給PG狀態機,以後發送MOSDPGBackfill::OP_BACKFILL_FINISH_ACK消息給primary,primary的ReplicatedPG::do_backfill()函數收到MOSDPGBackfill::OP_BACKFILL_FINISH_ACK消息。對於非primary收到MOSDPGBackfill::OP_BACKFILL_PROGRESS消息後調用pg_info_t->set_last_backfill()更新pg_info_t.last_backfill爲消息中提供的m->last_backfill值;
5)對於PG當前狀態爲PG_STATE_RECOVERING時,若此時仍然須要進行backfill操做(needs_backfill() return true),則發送RequestBackfill()事件,不然發送AllReplicasRecovered()事件。對於PG當前狀態不是PG_STATE_RECOVERING則發送Backfilled()事件。對於接收到AllReplicasRecovered()事件或Backfilled()事件的PG,將其狀態變動爲Recovered。在PG狀態機在Active狀態下收到AllReplicasActivated()事件後,設置all_replicas_activated=true。當PG狀態爲Recovered且all_replicas_activated=true,則PG發送GoClean()事件,以後PG狀態變動爲Clean狀態(即:active+clean狀態);
PG::discover_all_missing():該函數在執行完recovery操做後,仍然有missing的對象時被調用執行。該函數遍歷might_have_unfound集合,檢查peer_info[]和peer_missing[]集合中有關might_have_unfound集合變量的信息,確信仍然missing的,則將might_have_unfound集合中確信有missing的項插入到peer_missing_requested集合中,同時向有missing項的OSD節點發送獲取FULLLOG消息;