2016-10-26 更新start——————————————————————————————————————react
感謝 大水牛提出的疑問:對與本文中第二節,OSD恢復時經常使用的基本概念解析中對acting和up集合的描述問題,在研究ceph之初的時候我也不太懂acting和up集合的區別,而後查看資料從博客http://blog.csdn.net/changtao381/article/details/49125817引用了這部分概念的解釋,也感謝這位博主的努力和付出。在這裏面對acting和up集合的解釋確實出現了一點點的小紕漏。這裏更正一下。api
當@大水牛這位大神提出了對acting和up集合的質疑,而後我去查找了以前本身在分析ceph時抓取的一些log,去分析一下 acting和up集合。數組
log抓取的時機:殺掉一個osd.0進程,再從新啓動這個進程,而後分析他的log,在這個log里加了一些日誌供分析。截取部分日誌以下:app
7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 24/25/24) [2,1]/[2,1,0] r=2 lpr=25 pi=22-24/2 luod=0'0 crt=0'0 lcod 9'1 active+remapped] state<Started>: XYJ TEST advmap 1 Line 2: 2015-09-17 13:38:08.394649 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 24/25/24) [2,1]/[2,1,0] r=2 lpr=25 pi=22-24/2 luod=0'0 crt=0'0 lcod 9'1 active+remapped] state<Started/ReplicaActive/RepNotRecovering>: XYJ TEST exit Line 3: 2015-09-17 13:38:08.394674 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 24/25/24) [2,1]/[2,1,0] r=2 lpr=25 pi=22-24/2 luod=0'0 crt=0'0 lcod 9'1 active+remapped] state<Started/ReplicaActive>: XYJ TEST exit Line 4: 2015-09-17 13:38:08.394698 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 24/25/24) [2,1]/[2,1,0] r=2 lpr=25 pi=22-24/2 luod=0'0 crt=0'0 lcod 9'1 active+remapped] state<Started>: XYJ TEST exit Line 5: 2015-09-17 13:38:08.394740 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 24/25/24) [2,1]/[2,1,0] r=2 lpr=25 pi=22-24/2 luod=0'0 crt=0'0 lcod 9'1 active+remapped] state<Reset>: XYJ TEST Line 11: 2015-09-17 13:38:08.395107 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Reset>: XYJ TEST advmap Line 12: 2015-09-17 13:38:08.395172 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Reset>: XYJ TEST actmap Line 13: 2015-09-17 13:38:08.395198 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Reset>: XYJ TEST exit Line 14: 2015-09-17 13:38:08.395220 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Started>: XYJ TEST Line 16: 2015-09-17 13:38:08.395251 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Start>: XYJ TEST start with stary Line 18: 2015-09-17 13:38:08.395277 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Start>: XYJ TEST exit Line 20: 2015-09-17 13:38:08.395305 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Started/Stray>: XYJ TEST Line 100: 2015-09-17 13:38:08.411022 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Started/Stray>: XYJ TEST query Line 105: 2015-09-17 13:38:08.442529 7fba68b79700 10 osd.0 pg_epoch: 27 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Started>: XYJ TEST flush Line 113: 2015-09-17 13:38:09.394743 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Started>: XYJ TEST advmap 1 Line 114: 2015-09-17 13:38:09.394771 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Started/Stray>: XYJ TEST exit Line 115: 2015-09-17 13:38:09.394794 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Started>: XYJ TEST exit Line 116: 2015-09-17 13:38:09.394823 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/27/24) [0,2,1]/[2,1,0] r=2 lpr=27 pi=22-26/3 crt=0'0 lcod 9'1 remapped NOTIFY] state<Reset>: XYJ TEST Line 117: 2015-09-17 13:38:09.395203 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/28/28) [0,2,1] r=0 lpr=28 pi=22-27/4 crt=0'0 lcod 9'1 mlcod 0'0 inactive] state<Reset>: XYJ TEST advmap Line 118: 2015-09-17 13:38:09.395250 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/28/28) [0,2,1] r=0 lpr=28 pi=22-27/4 crt=0'0 lcod 9'1 mlcod 0'0 inactive] state<Reset>: XYJ TEST actmap Line 119: 2015-09-17 13:38:09.395272 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/28/28) [0,2,1] r=0 lpr=28 pi=22-27/4 crt=0'0 lcod 9'1 mlcod 0'0 inactive] state<Reset>: XYJ TEST exit Line 120: 2015-09-17 13:38:09.395293 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/28/28) [0,2,1] r=0 lpr=28 pi=22-27/4 crt=0'0 lcod 9'1 mlcod 0'0 inactive] state<Started>: XYJ TEST Line 121: 2015-09-17 13:38:09.395324 7fba68378700 10 osd.0 pg_epoch: 28 pg[0.0( v 9'2 (0'0,9'2] local-les=26 n=1 ec=1 les/c 26/26 27/28/28) [0,2,1] r=0 lpr=28 pi=22-27/4 crt=0'0 lcod 9'1 mlcod 0'0 inac
能夠看出pg0.0的集合變化,pg0.0是採用3副本模式。pg0.0的osd集合變化以下:函數
a.當丟失osd.0後 pg0.0 的集合變成了 [2,1]/[2,1] 因爲acting和up集合相同因此只顯示一個 [2,1]ui
b.當osd.0從新啓動後將osd.0加入pg0.0 則變成 [2,1]/[2,1,0]。this
c.根據crush規則從新計算pg0.0 的osd集合,變成了 [0,2,1]/[2,1,0]spa
d.最後當數據恢復完成後,pg0.0的osd集合爲[0,2,1]/[0,2,1]因爲acting和up集合相同因此只顯示一個[0,2,1].net
問題在於步驟c。在c中存在兩個集合,一個是根據crush算出的集合[0,2,1],另外一個是臨時的集合 [2,1,0]。須要肯定的是acting與up分別對應的是哪個集合? 重新來看下代碼便可知曉, pg狀態打印代碼以下:線程
5347:打印pginfo的信息。
5348:打印up集合的信息。
5349:若是acting集合與up集合不相同則打印acting集合。
如今再去看上面的log,就很容易對應起來了,這裏的up 對應的是crush根據當前的osdmap算出來的,acting集合爲臨時的pg集合。
因此你們在看第二節對名詞解釋的時候注意區分。
感謝@大水牛提出的意見
2016-10-26 更新end——————————————————————————————————————
集羣中的設備異常(異常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操做。
(引用自http://blog.csdn.net/changtao381/article/details/49125817,感謝該博主)
上一節當中已經講述了PG的Peering的過程,Peering就是PG在副本所在的OSD發生變化時的狀態演變過程。這一節也和Peering過程是分不開的,可是重點是來說述數據恢復過程當中涉及到的關鍵步驟。
先來解釋一下基本的概念。
1. 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。
2. 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]。
3.epoch:當集羣中的OSD發生變化,則就會產生新的OSDmap,每一個OSDmap都對應一個epoch,epoch按着前後順序單調遞增。epoch越大說明OSDmap越新。
4.current_interval、past_interval:每一個PG都有interval。 interval 是一個epoch的序列,在這個interval內,可能存在多個epoch,可是PG的成員卻不會改變。若是PG的成員發生變化,則會造成new interval。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.pg_log:pg_log是用於恢復數據重要的結構,每一個pg都有本身的log。對於pg的每個object操做都記錄在pg當中。
__s32 op; 操做的類型
hobject_t soid; 操做的對象
eversion_t version, prior_version, reverting_to; 操做的版本
struct pg_info_t
{
spg_t pgid;
eversion_t last_update; //pg 最後一次更新的eversion
//recovery完成後的最後一個everson,也就是pg處於clean狀態的最後一次操做的 eversion
eversion_t last_complete;
// last epoch at which this pg started on this osd 這個pg在這個osd上的最近的的開始的epoch,也就是最近一次peering完成後的epoch
epoch_t last_epoch_started;
// last user object version applied to store
version_t last_user_version;
eversion_t log_tail; // oldest log entry.
// objects >= this and < last_complete may be missing
hobject_t last_backfill;
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
}
1. 生成past_interval (要根據interval肯定每一個interval期間的osd集合)。
2.根據past_interval 選取參與peering過程的osd集合 build_prior。
3. 拉取prior集合中全部osd的pg_info。
4. 選取權威的osd。
5.拉取權威osd的log與info。與本地log合併。造成本地missing結構
6.拉取其餘osd的log與info。與auth log對比,將差別log發送給peer。而且在本地造成peer_missing結構。
7.根據missing和peer_missing結構可知丟失數據定位。
8.peering處理成功,開始進行數據的恢復。
詳細分析這個過程主要涉及到peering過程的準備與recovering的數據恢復過程。在peering準備過程如上圖描述。
peering過程準備工做:
肯定參與peering過程的osd集合。
該集合中合併出最權威的log記錄。
每個osd缺失而且須要恢復的object。
須要恢復的object能夠從哪一個osd上進行拷貝。
4.1 恢復的前驅peering過程的準備工做:
在進行解析peering過程當中,確定和上一節講述的內容是分不開的。可是這裏先去除其餘異常整理代碼,直接從GetInfo處理函數講起(PG::RecoveryState::GetInfo::GetInfo(my_context ctx))。
在GetInfo函數中先來看一下:
7372:調用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。
7379:根據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狀態的。
7384:根據priorset 集合,開始獲取集合中的全部osd的info。這裏會向全部的osd發送請求info的req(PG::RecoveryState::GetInfo::get_infos())。發送請求後等待回覆。
回覆的處理函數:PG::RecoveryState::GetInfo::react(const MNotifyRec& infoevt)
主要調用了pg->proc_replica_info進行處理:1.將info放入peerinfo數組中。2.合併history記錄。 在這裏會等待全部的副本都回復info信息。進入下一個狀態GetLog。
在GetLog中處理(PG::RecoveryState::GetLog::GetLog):
經過pg->choose_acting(auth_log_shard)選擇acting集合和auth_osd.
向auth_osd發送查詢 log的req。
choose_acting中主要進行了兩項重要的措施:
find_best_info,查找一個最優的osd。
calc_replicated_acting,選擇參與peering、recovering的osd集合。
在 find_best_info中查找最優的osd時,判斷的條件的優先級有三個:最大的last_update、最小的log_tail、當前的primary。
在calc_replicated_acting中主要進行一下幾種分析:
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的成員)。
以上在GetLog中的工做就都完成了,而後想best_osd發送log請求,等待best_osd回覆。
best_osd回覆PG::RecoveryState::GetLog::react(const GotLog&):
使用pg->proc_master_log()處理來自best_osd的log。
跳轉到GetMissing的處理
在pg->proc_master_log()中
0281:調用merge_log函數,該函數對log進行合併,造成一個權威順序完整的一個log。包括日誌先後的修補,並且最重要的是修補的過程當中,統計了本地副本中須要恢復object的狀況missing.add_next_event(ne)。這裏已經開始統計missing結構了。
0284:保存來自best_log的oinfo到本地的peer-info數組中。
0296:須要對history信息進行合併。
0299:將missing結構統計到本地的peer_missing結構中。
這是一個很重要的處理過程,涉及到兩個重要的點,
auth_log:一個是auth_log的合併,最大最權威的log,恢復數據要根據這裏進行。
missing:另外就是合併log過程當中發現本地副本須要恢復的object集合。
omissing:auth_osd須要進行恢復的object集合。
接下來該進入GetMissing處理中(PG::RecoveryState::GetMissing::GetMissing(my_context ctx)):
循環遍歷actingbackfill 成員,拉取全部成員的log和missing信息。等待回覆。
在回覆PG::RecoveryState::GetMissing::react(const MLogRec& logevt)中:
接收到其餘osd發回的log信息而且進行處理,主要爲pg->proc_replica_log處理。
在proc_replica_log中對peer_log進行修剪,丟棄那些不完整不可用的log。整理接收到的oinfo到peerinfo中,omissing到peer_missing中。接下來能夠選擇跳過NeedUpThru(由於 我不知道這個什麼做用),直接來到active狀態。
在active處理 PG::RecoveryState::Active::Active(my_context ctx):
在這裏主要調用了pg->activate()處理。
這裏首先進行了一些細節的處理,這些細節對於流程的控制起到很重要的做用,可是不是關鍵的流程,下面截取一些關鍵流程。
1769:這裏主要由primary osd進行處理。發起流程。
1775:開始循環處理 replica osd,由於這些osd都添加在actingbackfill中。這些replica可能有缺失log,因此要進行log的修補。
1858:若是存在一些replica osd須要進行log的修補工做,則建立一個message用於傳遞修補的log。
1863:這裏知道須要修補的log從last_update開始。使用copy_after拷貝以後的log。
1876:判斷若是不是全部的object都修復了,則須要記錄missing結構。
1878:循環遍歷須要修補的log list。
1881:判斷這個log記錄的 object是否是還沒修補呢。
1883:若是這個object還沒來得及修補,則添加到pm中,記錄這object須要被恢復。
1893:將這個用於修補log的message發送給對應的osd。等待迴應。
上面一部分主要進行了每一個osd的差別log整理工做,而且將這個log組織在message中,準備發送給對應的osd。並且根據缺失log肯定了該osd須要恢復的object,放在pm的結構中。
對於每個peer osd 都進行了缺失log的整理工做,而且也整理了peer_missing 存放每一個osd須要恢復的object。如今知道了每一個osd須要恢復的object,可是不知道這些object須要從哪一個osd上拉取object數據,因此還須要肯定這些須要恢復的object能夠在哪一個osd進行拉取數據。這部分處理能夠看下這個圖。
1. 首先這裏都知道了pg->peer_missing結構,由於上面根據缺失log可知這些須要恢復的object,這些object記錄在peermissing->missing結構中。
2.接下來結果轉換將整理統計 哪些object須要恢復,固然將整理的結果添加到pg->missing_loc->needs_recovery_map結構中保存。這樣能夠按着object進行恢復。
3.知道了哪些object須要進行恢復,還要繼續肯定這些object能夠從哪些osd中拉取,將每一個object能夠拉取數據的osd 集合使用 pg->Missing_loc->missing_loc保存。
通過上面的步驟能夠獲得一下條件:
1. 每一個osd須要恢復的object集合 ,保存在peer_missing結構中。
2. 此次recovery過程全部須要恢復的object,保存在need_recovery_map中。
3.這些須要恢復的object能夠從哪些osd上拉取數據,保存在 missing_loc結構中。
接下來看代碼是怎麼實現的,由於前面已經說過了peermissing的組織,因此這裏直接從第二條need_recovery_map結構組織開始(仍是pg->activate()):
1913:仍然循環處理 actingbackfill 列表中的全部的成員。
1917:若是是primary osd則添加primary osd的missing,這裏使用add_active_missing()函數,就是將丟失的object直接添加到need_recovery_map中。
1922:這裏是將其餘osd的missing中記錄的object添加到need_recovery_map中。
接下來就是要進行第三個結構missing_loc的組織了:
1940:先開始處理本地missing結構,使用add_source_info()函數處理。該函數處理主要是對比當前osd的missing列表和need_recovery列表,若是不在missing列表,卻在need_recovery_map列表的object,能夠從本osd上拉取數據,將這個osd記錄起來missing_loc[soid].insert(fromosd)。
1941:開始循環處理其餘的osd。
1950:處理其餘osd上的missing結構,處理方法同1941。
這樣三部分的數據都有了,下面就能夠進行數據的恢復了。
這樣等待全部的osd都發回了確認ack(差別日誌發送給osd)信息,進入以下處理的函數中PG::RecoveryState::Active::react(const AllReplicasActivated &evt),這裏主要調用了pg->on_activate(),該方法是由ReplicatedPG::on_activate()實現的。通過一系列的變化(上一節中詳細講述了),達到recovering狀態。最後將這個pg添加到了osd->recovery_queue中,recovery_queue是一個工做隊列,會有專門處理的線程來進行處理。該線程的主處理函數爲void OSD::do_recovery(PG *pg, ThreadPool::TPHandle &handle),解決着調用ReplicatedPG::start_recovery_ops()。
9497:獲取主osd上的missing 統計結果。
9499:獲取主osd上缺失而且須要恢復的object數量。
9500:獲取定位缺失數據中 缺乏數據源的object數量。
9507:若是 主osd上丟失的object 數量 與 沒法定位數據源的object數量相同。也就是主osd上丟失的object暫時沒法恢復。
9511:開始先恢復replicas osd上的數據。
9514:直到 replicas osd上數據全都恢復完畢,或者無數據能夠恢復時。
9517:開始進行 primary osd上的數據恢復。
9520:若是前面兩項都進行了數據恢復,可是仍然有object沒有被恢復,則再次恢復replicas上缺失的object。
上面進行了三次恢復的操做,第一次恢復replicas副本,第二次恢復primary 副本,第三次恢復replicas副本,爲了恢復數據的流控,在恢復的時候設置了閥值,每次恢復的上限就是閥值,超過閥值後,會將該pg從新添加到recovery_wq隊列中,等待下次被處理,一樣上面的三次恢復操做之間存在依賴關係,因此必須一個恢復完再嘗試恢復第二個,若是恢復完第一個後,再也不進行第二個,第三的恢復,直接從新添加到recovery_wq中,直到第一個被恢復完成,再次添加到recovery_wq中,下次纔會進行第二個恢復操做。
全部的數據恢復都要通過primary osd,
1. 若是primary osd出現數據丟失object,則由primary osd主動pull拉取replicas osd上的object數據。
2. 若是 replicas osd 上出現數據丟失object,則由primary osd 主動push 推送replicas osd上的 object數據。
3.若是primary osd 和 部分replica osd缺失object數據,則先由primary osd從正常的replica osd上拉取數據,進行本地恢復。下一次再把數據推送到須要恢復的osd上。
2016-11-05:更新start————————————————————————————————
恢復數據的過程,描述起來很複雜,較爲難懂。這篇博客介紹的是數據恢復的大致過程,不少細節沒有講,每一個人的角度不一樣,很難都兼顧獲得,因此又從新整理了一個數據恢復的圖。藉此圖說明在數據移動的過程當中,發生osd變化,而且伴有寫操做的複雜狀況,看ceph是如何處理的。這樣你們就能更清楚明白的讀懂ceph數據恢復過程。
1.寫入object1。初始狀態pg ,他由osd.0 、osd.一、osd.2組成的三副本形式,這時up集合爲[0,1,2],acting集合爲[0,1,2],acting_primary 爲osd 0。這時該pg已經完成寫入object1,那osd0,1,2上都保存這object1。這時pg處於interval0.
2.加入osd3。當集羣添加osd3.有的pg上的數據會發生移動,剛巧這個pg的up由[0,1,2]變成[0,1,3],這時的acting也變成了[0,1,3],acting_primary 爲osd 0。可是osd.3上沒有object1,標記爲虛線框黃底色的object1,就會發生數據移動,由osd.0 向 osd.3上拷貝數據(該過程是recovery或者backfill,肯定是哪種,要根據pglog來決定)。當拷貝數據的過程當中發生,數據寫入。加入osd3後,pg的up集合由[0,1,2]變成[0,1,3],因此pg此時爲interval 1。
3.寫入object2。在寫入pg的時候,數據會寫三個副本,分別寫到osd0,1,3中。osd0,1中有object1,2。osd3中只有object2,osd3中的object1還在數據恢復中。因爲up集合沒發生變化,因此此時pg仍然是interval 1
4.加入osd4。在osd3沒有數據恢復完成,就加入了osd4,此時pg的up集合由[0,1,3]變成了[0,4,3]。因此pg進入到了interval 2。osd0保存了object1,object2。osd3保存了object2,須要恢復object1。osd4上須要恢復object1,object2。這時的acting集合仍然是[0,4,3]. acting_primary 爲osd 0。pg又從新進入到數據恢復的過程,恢復osd4,3上的數據。
5.寫入object3。在osd4,osd3上的數據恢復沒有完成的時候,又寫入object3,這個object3寫入到osd0,4,3上。寫入數據完成。此時 osd4仍然須要恢復object1,object2。osd3仍然須要恢復數據object1。
6.加入osd5。在osd4,osd3上的數據沒有恢復完成前,又加入了osd5 引發了pg的up集合變化,pg的up集合由[0,4,3]變成[5,4,3],因爲up集合變化,因此進入interval 3。可是這時osd5,osd4,osd3上都有數據要恢復,在選取acting集合的時候要借鑑interval 2 中的acting集合,爲了能恢復數據,這時的acting集合爲[0,4,5,3],在肯定acting_primary osd的時候,若是pg進入recoving狀態 則選擇osd5爲acting_primary osd,若pg進入backfill狀態則選擇osd0爲acting_primary osd。假設咱們這時是backfill的pg。則選舉osd0爲acting_primay。acting_primary 的做用就是用來處理客戶端的請求的。此時osd3,osd4,osd5都有object須要恢復,從新開始恢復數據。
7.寫入object4。在osd4,osd5,osd3 都有數據須要恢復,可是osd0爲acting_primary,因此osd0 接收數據分紅4份,分別寫入osd0,osd3,osd4,osd5。此時osd0上有 object1,object2,object3,object4。osd3上有object2,object3,object4,須要恢復object1。osd4上有object3,object4,須要恢復object1,object2。osd5上只有object4,須要恢復object1,object2,object3。
8.等待數據恢復完成。當數據恢復完成時,會發送事件給pgmonitor,此時會從新發起pg的peering過程。可是此時up集合爲[5,4,3],因爲數據恢復完成不須要借鑑interval2,因此acting集合爲[5,4,3] ,在acting集合中會選舉osd5爲acting_primary,將osd0踢出pg。此後由osd5負責 處理客戶端的請求。
若有錯誤,請批評指正。歡迎留言。
2016-11-05:更新end————————————————————————————————