【ceph】Ceph 存储中 PGMap、OSDMap 和xxMap
目錄
簡介
OSDMap
PG 和 PGMap
OSDMap 機(jī)制淺析
OSDMap 代碼淺析
二、OSDMap模塊數(shù)據(jù)結(jié)構(gòu)
1.osd_info_t
2.osd_xinfo_t
3.OSDMap
4.Incremental
三、MOSDMap消息
四、OSDMap更新機(jī)制
五、OSDMap要點(diǎn)分析
六、獲取OSDMap
1.查看當(dāng)前集群最新epoch的OSDMap信息
七、與OSDMap相關(guān)的參數(shù)配置
八、疑點(diǎn)
作者bandaoyu,隨時(shí)更新本文鏈接:https://blog.csdn.net/bandaoyu/article/details/123097837
本文主要介紹ceph分布式存儲架構(gòu)中OSDMap和PGMap的原理及相關(guān)重要信息。
什么是OSDMap 和 PGMap
(摘自:https://blog.51cto.com/wendashuai/2511361,源文:Analizar Ceph: OSD, OSDMap y PG, PGMap - programador clic)
Monitor 作為Ceph的 Metada Server 維護(hù)了集群的信息,它包括了6個 Map,分別是 MONMap,OSDMap,PGMap,LogMap,AuthMap,MDSMap。其中 PGMap 和 OSDMap 是最重要的兩張Map,本文會重點(diǎn)介紹。
OSDMap
????????OSDMap 是 Ceph 集群中所有 OSD 的信息,所有 OSD 狀態(tài)的改變?nèi)邕M(jìn)程退出,OSD的加入和退出或者OSD的權(quán)重的變化都會反映到這張 Map 上。這張 Map 不僅會被 Monitor 掌握,OSD 節(jié)點(diǎn)和 Client 也會從 Monitor 得到這張表,因此實(shí)際上我們需要處理所有 “Client” (包括 OSD,Monitor 和 Client)的 OSDMap 持有情況。
????????實(shí)際上,每個 “Client” 可能會具有不同版本的 OSDMap,當(dāng) Monitor 所掌握的權(quán)威 OSDMap 發(fā)生變化時(shí),它并不會發(fā)送 OSDMap 給所有 “Client” ,而是需要那些感知到集群變化的 “Client” 會被 Push,比如一個新的 OSD 加入集群會導(dǎo)致一些 PG 的遷移,那么這些 PG 的 OSD 會得到通知。除此之外,Monitor 也會隨機(jī)的挑選一些 OSD 發(fā)送 OSDMap。
????????那么如何讓 OSDMap 慢慢傳播呢?
????????比如 OSD.a, OSD.b得到了新的 OSDMap,那么 OSD.c 和 OSD.d 可能部分 PG 也會在 OSD.a, OSD.b 上,這時(shí)它們的通信就會附帶上 OSDMap 的 epoch,如果版本較低,OSD.c 和 OSD.d 會主動向 Monitor pull OSDMap,而部分情況 OSD.a, OSD.b 也會主動向 OSD.c 和 OSD.d push 自己的 OSDMap (如果更新)。因此,OSDMap 會在接下來一段時(shí)間內(nèi)慢慢在節(jié)點(diǎn)間普及。在集群空閑時(shí),很有可能需要更長的時(shí)間完成新 Map的更新,但是這并不會影響 OSD 之間的狀態(tài)一致性,因?yàn)镺SD沒有得到新的Map,所以它們不需要知曉新的OSDMap變更。
????????Ceph 通過管理多個版本的 OSDMap 來避免集群狀態(tài)的同步,這使得 Ceph 絲毫不會畏懼在數(shù)千個 OSD 規(guī)模的節(jié)點(diǎn)變更,而導(dǎo)致集群可能出現(xiàn)的狀態(tài)同步的問題。
新OSD啟動OSDMap的變化
??????????當(dāng)一個新的 OSD 啟動時(shí),這時(shí) Monitor 的最新 OSDMap 并沒有該 OSD 的情況,因此該 OSD 會向 Monitor 申請加入,Monitor 在驗(yàn)證其信息后會將其加入 OSDMap 并標(biāo)記為IN,并且將其放在 Pending Proposal 中會在下一次 Monitor “討論”中提出,OSD 在得到 Monitor 的回復(fù)信息后發(fā)現(xiàn)自己仍然沒在 OSDMap 中會繼續(xù)嘗試申請加入,接下來 Monitor 會發(fā)起一個 Proposal ,申請將這個 OSD 加入 OSDMap 并且標(biāo)記為 UP 。然后按照 Paxos 的流程,從 proposal->accept->commit 到最后達(dá)成一致,OSD 最后成功加入 OSDMap 。當(dāng)新的 OSD 獲得最新 OSDMap 發(fā)現(xiàn)它已經(jīng)在其中時(shí)。這時(shí),OSD 才真正開始建立與其他OSD的連接,Monitor 接下來會開始給他分配PG。
OSD crash
?
圖C: OSD down過程
????????當(dāng)一個 OSD 因?yàn)橐馔?crash 時(shí),其他與該 OSD 保持 Heartbeat 的 OSD 都會發(fā)現(xiàn)該 OSD 無法連接,在匯報(bào)給 Monitor 后,該 OSD 會被臨時(shí)性標(biāo)記為 OUT,所有位于該 OSD 上的 Primary PG 都會將 Primary 角色交給其他 OSD(下面會解釋)。
PG 和 PGMap
?圖D: PG和PGMap
????????PG(Placement Group)是 Ceph 中非常重要的概念,它可以看成是一致性哈希中的虛擬節(jié)點(diǎn),維護(hù)了一部分?jǐn)?shù)據(jù)并且是數(shù)據(jù)遷移和改變的最小單位。它在 Ceph 中承擔(dān)著非常重要的角色,在一個 Pool 中存在一定數(shù)量的 PG (可動態(tài)增減),這些 PG 會被分布在多個 OSD ,分布規(guī)則可以通過 CRUSH RULE 來定義。
????????Monitor 維護(hù)了每個Pool中的所有 PG 信息,比如當(dāng)副本數(shù)是三時(shí),這個 PG 會分布在3個 OSD 中,其中有一個 OSD 角色會是 Primary ,另外兩個 OSD 的角色會是 Replicated。Primary PG負(fù)責(zé)該 PG 的對象寫操作,讀操作可以從 Replicated PG獲得。而 OSD 則只是 PG 的載體,每個 OSD 都會有一部分 PG 角色是 Primary,另一部分是 Replicated,當(dāng) OSD 發(fā)生故障時(shí)(意外 crash 或者存儲設(shè)備損壞),Monitor 會將該 OSD 上的所有角色為 Primary 的 PG 的 Replicated 角色的 OSD 提升為 Primary PG,這個 OSD 所有的 PG 都會處于 Degraded 狀態(tài)。然后等待管理員的下一步?jīng)Q策,所有的 Replicated 如果原來的 OSD 無法啟動, OSD 會被踢出集群,這些 PG 會被 Monitor 根據(jù) OSD 的情況分配到新的 OSD 上。
PG的peering過程
圖E: PG的peering過程
????????在 Ceph 中,PG 存在多達(dá)十多種狀態(tài)和數(shù)十種事件的狀態(tài)機(jī)去處理 PG 可能面臨的異常, Monitor 掌握了整個集群的 OSD 狀態(tài)和 PG 狀態(tài),每個PG都是一部分 Object 的擁有者,維護(hù) Object 的信息也每個 PG 的責(zé)任,Monitor 不會掌握 Object Level 的信息。因此每個PG都需要維護(hù) PG 的狀態(tài)來保證 Object 的一致性。但是每個 PG 的數(shù)據(jù)和相關(guān)故障恢復(fù)、遷移所必須的記錄都是由每個 PG 自己維護(hù),也就是存在于每個 PG 所在的 OSD 上。
???????????PGMap 是由 Monitor 維護(hù)的所有 PG 的狀態(tài),每個 OSD 都會掌握自己所擁有的 PG 狀態(tài),PG 遷移需要 Monitor 作出決定然后反映到 PGMap 上,相關(guān) OSD 會得到通知去改變其 PG 狀態(tài)。在一個新的 OSD 啟動并加入 OSDMap 后,Monitor 會通知這個OSD需要創(chuàng)建和維護(hù)的 PG ,當(dāng)存在多個副本時(shí),PG 的 Primary OSD 會主動與 Replicated 角色的 PG 通信并且溝通 PG 的狀態(tài),其中包括 PG 的最近歷史記錄。通常來說,新的 OSD 會得到其他 PG 的全部數(shù)據(jù)然后逐漸達(dá)成一致,或者 OSD 已經(jīng)存在該 PG 信息,那么 Primary PG 會比較該 PG 的歷史記錄然后達(dá)成 PG 的信息的一致。這個過程稱為 Peering ,它是一個由 Primary PG OSD 發(fā)起的“討論”,多個同樣掌握這個 PG 的 OSD 相互之間比較 PG 信息和歷史來最終協(xié)商達(dá)成一致。
OSDMap 機(jī)制淺析
(摘自:https://cloud.tencent.com/developer/article/1664568)
????????OSDMap 機(jī)制是 Ceph 架構(gòu)中非常重要的部分,PG 在 OSD 上的分布和監(jiān)控由 OSDMap 機(jī)制執(zhí)行。OSDMap 機(jī)制和 CRUSH 算法一起構(gòu)成了 Ceph 分布式架構(gòu)的基石。
OSDMap 機(jī)制主要包括如下3個方面:
1、Monitor 監(jiān)控 OSDMap 數(shù)據(jù),包括 Pool 集合,副本數(shù),PG 數(shù)量,OSD 集合和 OSD 狀態(tài)。
2、OSD 向 Monitor 匯報(bào)自身狀態(tài),以及監(jiān)控和匯報(bào) Peer OSD 的狀態(tài)。
3、OSD 監(jiān)控分配到其上的 PG , 包括新建 PG , 遷移 PG , 刪除 PG 。
????????在整個 OSDMap 機(jī)制中,OSD充分信任 Monitor, 認(rèn)為其維護(hù)的 OSDMap 數(shù)據(jù)絕對正確,OSD 對 PG 采取的所有動作都基于 OSDMap 數(shù)據(jù),也就是說 Monitor 指揮 OSD 如何進(jìn)行 PG 分布。
????????在 OSDMap 數(shù)據(jù)中 Pool 集合,副本數(shù),PG 數(shù)量,OSD 集合這 4 項(xiàng)由運(yùn)維人員來指定,雖然 OSD 的狀態(tài)也可以由運(yùn)維人員進(jìn)行更改,但是實(shí)際運(yùn)行的 Ceph 集群 A 中,從時(shí)間分布來看,運(yùn)維人員對 Ceph 集群進(jìn)行介入的時(shí)間占比很小,因此 OSD 的故障(OSD 狀態(tài))才是 Monitor 監(jiān)控的主要目標(biāo)。
????????OSD 故障監(jiān)控由 Monitor 和 OSD 共同完成,在 Monitor 端,通過名為 OSDMonitor 的 PaxosService 線程實(shí)時(shí)的監(jiān)控 OSD 發(fā)來的匯報(bào)數(shù)據(jù)(當(dāng)然,也監(jiān)控運(yùn)維人員對 OSDMap 數(shù)據(jù)進(jìn)行的操作)。在 OSD 端,運(yùn)行一個 Tick 線程,一方面周期性的向 Monitor 匯報(bào)自身狀態(tài);另外一方面,OSD 針對 Peer OSD 進(jìn)行 Heartbeat 監(jiān)控,如果發(fā)現(xiàn) Peer OSD 故障,及時(shí)向 Monitor 進(jìn)行反饋。具體的 OSD 故障監(jiān)控細(xì)節(jié)本文不做分析。
OSDMap 機(jī)制中的第1點(diǎn)和第2點(diǎn)比較容易理解,下面本文主要針對第3點(diǎn)進(jìn)行詳細(xì)分析。
如上圖所示,在3個 OSD 的 Ceph 集群中,Pool 的副本數(shù)為3,某個 PG 的 Primary OSD 為 OSD0, 當(dāng) Monitor 檢測到 3 個 OSD 中的任何一個 OSD 故障,則發(fā)送最新的 OSDMap 數(shù)據(jù)到剩余的 2 個 OSD 上,通知其進(jìn)行相應(yīng)的處理。
如上圖所示,OSD 收到 MOSDMap 后,主要進(jìn)行3個方面的處理
- ObjectStore::Transaction::write(coll_t::meta()) 更新 OSDMap 到磁盤,保存在目錄 /var/lib/ceph/OSD/ceph-<id>/current/meta/,將 OSDMap 數(shù)據(jù)持久化,起到類似于 log 的作用。
- OSD::consume_map() 進(jìn)行 PG 處理,包括刪除 Pool 不存在的 PG; 更新 PG epoch(OSDmap epoch) 到磁盤(LevelDB); 產(chǎn)生 AdvMap 和 ActMap 事件,觸發(fā) PG 的狀態(tài)機(jī) state_machine 進(jìn)行狀態(tài)更新。
- OSD::activate_map() 根據(jù)需要決定是否啟動 recovery_tp 線程池進(jìn)行 PG 恢復(fù)。
在OSD端,PG 負(fù)責(zé) I/O 的處理,因此 PG 的狀態(tài)直接影響著 I/O,而 pgstate_machine 就是 PG 狀態(tài)的控制機(jī)制,但里面的狀態(tài)轉(zhuǎn)換十分的復(fù)雜,這里不做具體分析。
下面開始分析 PG 的創(chuàng)建,刪除,遷移
PG 的創(chuàng)建由運(yùn)維人員觸發(fā),在新建 Pool 時(shí)指定 PG 的數(shù)量,或增加已有的 Pool 的 PG 數(shù)量,這時(shí) OSDMonitor 監(jiān)控到 OSDMap 發(fā)生變化,發(fā)送最新的 MOSDMap 到所有的 OSD。
在 PG 對應(yīng)的一組 OSD 上,OSD::handle_pg_create() 函數(shù)在磁盤上創(chuàng)建 PG 目錄,寫入 PG 的元數(shù)據(jù),更新 Heartbeat Peers 等操作。
PG 的刪除同樣由運(yùn)維人員觸發(fā),OSDMonitor 發(fā)送 MOSDMap 到 OSD, 在 PG 對應(yīng)的一組 OSD 上,OSD::handle_PG _remove() 函數(shù)負(fù)責(zé)從磁盤上刪除PG 所在的目錄,并從 PGMap 中刪除 PG ,刪除 PG 的元數(shù)據(jù)等操作。
PG 遷移較為復(fù)雜,涉及到兩個OSD與monitor的協(xié)同處理。例如,向已有3個OSD的集群中新加入OSD3,導(dǎo)致 CRUSH 重新分布 PG , 某個 PG 的分配變化結(jié)果為 [0, 1, 2 ] -> [3, 1, 2]。當(dāng)然,CRUSH 的分配具有隨機(jī)性,不同的 PG 中,OSD3 既可能成為 Primary OSD,也可能成為 Replicate OSD, 這里取 OSD3 作為 Primary OSD為例。
新加入的OSD3取代了原有的 OSD0 成為 Primary OSD, 由于 OSD3 上未創(chuàng)建 PG , 不存在數(shù)據(jù),那么 PG 上的 I/O 無法進(jìn)行,因此,這里引入 PG Temp 機(jī)制,即 OSD3 向 Monitor 發(fā)送 MOSDPG Temp,把 Primary OSD 指定為OSD1, 因?yàn)?OSD1 上保存了 PG 的數(shù)據(jù),Client 發(fā)送到 PG 的請求都被轉(zhuǎn)發(fā)到了 OSD1;與此同時(shí),OSD1 向 OSD3 發(fā)送 PG 的數(shù)據(jù),直到 PG 數(shù)據(jù)復(fù)制完成,OSD1 將 Primary OSD 的角色交還給 OSD3,Client 的 I/O 請求直接發(fā)送的 OSD3,這樣就完成了 PG 的遷移。整個過程如下圖所示。
另外一種 PG 的遷移情景是 OSD3 作為 Replicate OSD 時(shí),由 Primay OSD 向 OSD3 進(jìn)行 PG 數(shù)據(jù)遷移,比上述 PG 遷移過程更為簡單,這里不再詳述。
本文從 PG 的視角闡述了 OSDMap 機(jī)制的基本原理,描述了 Monitor, OSD, PG 三者之間的關(guān)聯(lián)。 在實(shí)際運(yùn)維中,我們常常對于 OSD 狀態(tài)和數(shù)量的變化引起的 PG 狀態(tài)的變化感到疑惑,希望本文能夠?qū)鉀Q的 PG 狀態(tài)問題帶來啟發(fā)。
OSDMap 代碼淺析
(摘自:https://gitee.com/wanghongxu/cephknowledge/blob/master/Ceph-OSDMap.md)
一、OSDMap模塊簡介
OSDMap是關(guān)于Ceph集群中所有OSD的信息,所有OSD節(jié)點(diǎn)的改變,踢出或加入集群,節(jié)點(diǎn)的權(quán)重改變都會 反映到這個Map上面。在Ceph集群中,所有的Server(MDS,MON和OSD...)和Client都得掌握這個 Map。
每個角色都可能持有不同epoch的OSDMap,如果客戶端持有的map是過期的,可以利用OSD的回復(fù)來判斷。
二、OSDMap模塊數(shù)據(jù)結(jié)構(gòu)
1.osd_info_t
struct osd_info_t {epoch_t last_clean_begin;epoch_t last_clean_end;epoch_t up_from; // epoch osd marked upepoch_t up_thru; // lower bound on actual osd death (if > up_from)epoch_t down_at; // upper bound on actual osd death (if > up_from)epoch_t lost_at; // last epoch we decided data was "lost" ... };-
last_clean_begin和last_clean_end: 以一個clean的osd關(guān)閉而結(jié)束的最終區(qū)間
-
up_from: osd被標(biāo)記為up時(shí)的epoch
-
up_thru: 實(shí)際osd終止時(shí)的下界(如果它大于up_from)
-
down_at: 實(shí)際osd終止時(shí)的上界(如果它大于up_from)
-
lost_at: 決定數(shù)據(jù)已經(jīng)丟失時(shí)的最終epoch
osd_info_t結(jié)構(gòu)用來表述隨著時(shí)間推移,各個狀態(tài)的epoch。
我們跟蹤在OSD存活且Clean時(shí)候的兩個區(qū)間,最新的是[up_from,up_thru]。up_thru是已知OSD已經(jīng) 啟動的最后的epoch,即實(shí)際osd死亡的下界,down_at(如果它大于up_from)是實(shí)際osd死亡的上界。 第二個是last_clean區(qū)間[first,last],在這種情況下,last區(qū)間是已知完成的最后一個epoch,或 在此期間osd徹底關(guān)閉。如果可能的話,將last推送到osd被標(biāo)記為down時(shí)的epoch。
lost_at用來允許build_prior不等待一個osd恢復(fù)的情況下進(jìn)行。在某些情況下,進(jìn)度可能被阻止, 因?yàn)閛sd可能包含更新(即,pg可能在一個區(qū)間內(nèi)丟失讀寫)。如果osd無法在線,我們可以在已知丟失一些 確認(rèn)寫的情況下讓強(qiáng)制進(jìn)行。如果osd過會又好了,那也沒關(guān)系,但是這些寫依然會丟失(不同的對象會被 丟棄)。
2.osd_xinfo_t
struct osd_xinfo_t {utime_t down_stamp;float laggy_probability;__u32 laggy_interval;uint64_t features;__u32 old_weight;osd_xinfo_t() : laggy_probability(0), laggy_interval(0),features(0), old_weight(0) {} ... };osd_xinfo_t結(jié)構(gòu)為OSD的一些擴(kuò)展信息
字段解釋:
-
down_stamp: osd被標(biāo)記為down時(shí)的時(shí)間戳
-
laggy_probability: 滯后概率,編碼為__u32,0表示沒有滯后,0xffffffff表示滯后
-
laggy_interval: 被標(biāo)記為滯后和恢復(fù)中的平均間隔
-
features: 應(yīng)該知道的osd所支持的特性
-
old_weight: 自動標(biāo)記為out之前的權(quán)重
3.OSDMap
class OSDMap { public:class Incremental {...}; private:uuid_d fsid;epoch_t epoch; // what epoch of the osd cluster descriptor is thisutime_t created, modified; // epoch start timeint32_t pool_max; // the largest pool num, everuint32_t flags;int num_osd; // not saved; see calc_num_osdsint num_up_osd; // not saved; see calc_num_osdsint num_in_osd; // not saved; see calc_num_osdsint32_t max_osd;vector<uint8_t> osd_state;struct addrs_s {vector<ceph::shared_ptr<entity_addr_t> > client_addr;vector<ceph::shared_ptr<entity_addr_t> > cluster_addr;vector<ceph::shared_ptr<entity_addr_t> > hb_back_addr;vector<ceph::shared_ptr<entity_addr_t> > hb_front_addr;entity_addr_t blank;};ceph::shared_ptr<addrs_s> osd_addrs;vector<__u32> osd_weight;vector<osd_info_t> osd_info;ceph::shared_ptr< map<pg_t,vector<int32_t> > > pg_temp;ceph::shared_ptr< map<pg_t,int32_t > > primary_temp;ceph::shared_ptr< vector<__u32> > osd_primary_affinity;map<int64_t,pg_pool_t> pools;map<int64_t,string> pool_name;map<string,map<string,string> > erasure_code_profiles;map<string,int64_t> name_pool;ceph::shared_ptr< vector<uuid_d> > osd_uuid;vector<osd_xinfo_t> osd_xinfo;ceph::unordered_map<entity_addr_t,utime_t> blacklist;epoch_t cluster_snapshot_epoch;string cluster_snapshot;bool new_blacklist_entries;mutable uint64_t cached_up_osd_features;mutable bool crc_defined;mutable uint32_t crc; ...public:ceph::shared_ptr<CrushWrapper> crush; ... }OSDMap結(jié)構(gòu)存儲了包括集群id,epoch信息,創(chuàng)建及修改時(shí)間,pool信息,OSD信息(總的OSD,up的 OSD以及已經(jīng)加入集群的OSD)等等。
字段解釋:
-
fsid: 集群id
-
created, modified: epoch開始時(shí)間
-
pool_max: 從開始到現(xiàn)在的最大的pool id
-
pg_temp: 臨時(shí)pg映射(比如重建)
-
primary_temp: 臨時(shí)primary映射
-
flags: OSD的標(biāo)志位(以jewel為例,常見的標(biāo)志位有sortbitwise,require_jewel_osds),后 面會詳細(xì)敘述
標(biāo)志位在這里的作用是標(biāo)識OSD集群的當(dāng)前的一些狀態(tài),在使用ceph -s命令的時(shí)候會連同OSD信息(總 的OSD,up的OSD以及已經(jīng)加入集群的OSD)一起輸出,方便第一時(shí)間了解到一些異常情況。
另外,比如要設(shè)置OSD不進(jìn)行數(shù)據(jù)清洗,可以通過ceph命令設(shè)置標(biāo)志位為noscrub及nodeep-scrub
[root@cephdev ]# ceph osd set noscrub [root@cephdev ]# ceph osd set nodeep-scrub之后,再次查看集群狀態(tài)的時(shí)候就會發(fā)現(xiàn)多了這兩個標(biāo)志位:
[root@cephdev ]# ceph -s cluster a7aaeb51-63d4-4a07-9e5f-b94d970a686ehealth HEALTH_WARNnoscrub,nodeep-scrub,sortbitwise,require_jewel_osds flag(s) setmonmap e1: 1 mons at {a=127.0.0.1:6789/0}election epoch 4, quorum 0 aosdmap e10: 1 osds: 1 up, 1 inflags noscrub,nodeep-scrub,sortbitwise,require_jewel_osdspgmap v12042: 8 pgs, 1 pools, 0 bytes data, 0 objects840 MB used, 425 GB / 426 GB avail8 active+clean標(biāo)志位解釋:
-
nearfull: 磁盤快滿了
-
full: 磁盤已經(jīng)滿了
-
pauserd: 暫停所有讀
-
pausewr: 暫停所有寫
-
pauserec: 暫停恢復(fù)
-
noup: 禁止OSD啟動
-
nodown: 禁止標(biāo)記OSD為down
-
noout: 禁止OSD自動標(biāo)記為out
-
noin: 禁止OSD自動標(biāo)記為in
-
nobakfill: 禁止OSD進(jìn)行數(shù)據(jù)回填
-
norebalance: 禁止OSD回填,除非pg降級
-
norecover: 禁止OSD恢復(fù)和回填
-
noscrub: 設(shè)置OSD不進(jìn)行數(shù)據(jù)清洗
-
nodeep-scrub: 設(shè)置OSD不進(jìn)行數(shù)據(jù)的深度清洗
-
notieragent: 禁止tiering agent
-
sortbitwise: 使用hobject_t按位排序
-
require_jewel_osds: 這個標(biāo)記從J版開始有,標(biāo)志當(dāng)前OSD的版本,升級的時(shí)候有用
4.Incremental
class Incremental { public:/// feature bits we were encoded with. the subsequent OSDMap/// encoding should match.uint64_t encode_features;uuid_d fsid;epoch_t epoch; // new epoch; we are a diff from epoch-1 to epochutime_t modified;int64_t new_pool_max; //incremented by the OSDMonitor on each pool createint32_t new_flags;// full (rare)bufferlist fullmap; // in lieu of below.bufferlist crush;// incrementalint32_t new_max_osd;map<int64_t,pg_pool_t> new_pools;map<int64_t,string> new_pool_names;set<int64_t> old_pools;map<string,map<string,string> > new_erasure_code_profiles;vector<string> old_erasure_code_profiles;map<int32_t,entity_addr_t> new_up_client;map<int32_t,entity_addr_t> new_up_cluster;map<int32_t,uint8_t> new_state; // XORed onto previous state.map<int32_t,uint32_t> new_weight;map<pg_t,vector<int32_t> > new_pg_temp; // [] to removemap<pg_t, int32_t> new_primary_temp; // [-1] to removemap<int32_t,uint32_t> new_primary_affinity;map<int32_t,epoch_t> new_up_thru;map<int32_t,pair<epoch_t,epoch_t> > new_last_clean_interval;map<int32_t,epoch_t> new_lost;map<int32_t,uuid_d> new_uuid;map<int32_t,osd_xinfo_t> new_xinfo;map<entity_addr_t,utime_t> new_blacklist;vector<entity_addr_t> old_blacklist;map<int32_t, entity_addr_t> new_hb_back_up;map<int32_t, entity_addr_t> new_hb_front_up;string cluster_snapshot;mutable bool have_crc; ///< crc values are defineduint32_t full_crc; ///< crc of the resulting OSDMapmutable uint32_t inc_crc; ///< crc of this incremental }結(jié)構(gòu)Incremental表示OSDMap的增量版本
字段解釋:
-
encode_features: 編碼的特征位,隨后的OSDMap編碼應(yīng)該匹配
-
epoch: 新的epoch,從epoch-1到epoch的差異
-
new_pool_max: 當(dāng)每個pool被創(chuàng)建時(shí)由OSDMonitor增加
-
fullmap: 代替變量crush
-
have_crc: crc值定義時(shí)為true
-
inc_crc: 該增量的crc
三、MOSDMap消息
該消息用來獲取OSDMap信息。
四、OSDMap更新機(jī)制
在部署Monitor的時(shí)候,Monitor的KeyValueDB中只保存有epoch為1的osdmap,此時(shí)并沒有osd在集 群中,當(dāng)有OSD向Monitor去申請加入集群的時(shí)候,Monitor會將該OSD記錄在OSDMap中并寫入 KeyValueDB中。同時(shí),當(dāng)OSD有更新的時(shí)候,Monitor也會向OSD發(fā)送消息,然后OSD在 handle_osd_map中處理該消息。
當(dāng)部署OSD的時(shí)候,OSD::mkfs期間,會在OSD數(shù)據(jù)目錄創(chuàng)建meta目錄并初始化,當(dāng)OSD申請加入Ceph進(jìn) 群成功后,該目錄中就會存儲有每個epoch的OSDMap信息。
五、OSDMap要點(diǎn)分析
六、獲取OSDMap
Ceph提供了一個命令行工具osdmaptool,能讓你創(chuàng)建,查看和操作OSD映射關(guān)系。更重要的是,使用該 工具能獲取和導(dǎo)入Crushmap,并能模擬OSD的分布。
對于OSD來說,它保存了每個epoch時(shí)的OSDMap信息,默認(rèn)存放在OSD數(shù)據(jù)目錄的current/meta中。
1.查看當(dāng)前集群最新epoch的OSDMap信息
使用ceph osd dump命令可以查看當(dāng)前最新epoch的OSDMap信息。另外,確認(rèn)當(dāng)前OSDMap的最新 epoch后,在OSD數(shù)據(jù)目錄的meta目錄下找到以osdmap.{epoch}開頭的文件即是。查看OSDMap信息, 使用--print參數(shù)即可。
osdmaptool --print osdmap.736__0_AC955C85__noneosdmaptool: osdmap file 'osdmap.736__0_AC955C85__none' epoch 736 fsid d9650900-aa7a-465a-a483-a1e80e7c7efa created 2016-11-05 23:03:48.070572 modified 2017-03-16 10:12:26.756716 flags sortbitwise,require_jewel_osds,require_kraken_osdspool 7 'ssd' replicated size 3 min_size 1 crush_ruleset 0 object_hash rjenkinspg_num 320 pgp_num 320 last_change 578 flags hashpspool stripe_width 0removed_snaps [1~3] pool 19 'images' replicated size 3 min_size 1 crush_ruleset 0 object_hashrjenkins pg_num 2 pgp_num 2 last_change 620 flags hashpspool stripe_width 0removed_snaps [1~3]max_osd 6 osd.0 down out weight 0 up_from 393 up_thru 383 down_at 394 last_clean_interval[389,389)192.168.108.11:6800/2582901 192.168.108.11:6801/2582901 192.168.108.11:6802/2582901 192. 168.108.11:6803/2582901 autoout,exists cfc90fe4-952c-495a-9b25-3d81b4cf441e osd.1 up in weight 1 up_from 726 up_thru 735 down_at 724 last_clean_interval[717,725) 192.168.108.11:6800/1937953 192.168.108.11:6805/2937953192.168.108.11:6809/2937953 192. 168.108.11:6821/2937953 exists,up 6c614571-47db-490e-8f98-184e0e963430 osd.2 up in weight 1 up_from 735 up_thru 735 down_at 733 last_clean_interval[648,734) 192.168.108.12:6804/312646 192.168.108.12:6801/5312646192.168.108.12:6802/5312646 192.168.108.12:6803/5312646 exists,upe1ed8e95-3900-491d-8f30-a6db0259698a osd.3 up in weight 1 up_from 731 up_thru 735 down_at 729 last_clean_interval[636,730) 192.168.108.12:6800/1675 192.168.108.12:6809/9001675192.168.108.12:6810/9001675 192.168.108.12:6811/9001675 exists,upf9d5146d-5a77-4d8f-b893-50860fc50f02 osd.4 down out weight 0 up_from 250 up_thru 346 down_at 348 last_clean_interval[236,248) 192.168.108.13:6800/286476 192.168.108.13:6801/286476192.168.108.13:6802/286476 192.168.108.13:6803/286476 autoout,exists e4158fd4-84c6-41c6-b231-f4335edb9ff8 osd.5 down out weight 0 up_from 643 up_thru 649 down_at 651 last_clean_interval[566,567) 192.168.108.13:6801/3857339 192.168.108.13:6802/3857339192.168.108.13:6803/3857339 192. 168.108.13:6804/3857339 autoout,exists 9a17c533-24e0-4742-a977-2fdb14fd5415pg_temp 7.1 [1,3,2] ...如上所示,OSDMap中包含有當(dāng)前map的epoch,集群id,創(chuàng)建修改時(shí)間,標(biāo)志位,pool id,pool name, 副本數(shù),最小副本數(shù),使用的crush規(guī)則,pg和pgp數(shù)量,osd數(shù)量,各個osd在集群中的狀態(tài),權(quán)值, pg_temp映射關(guān)系等等。
注意:OSD中存儲的OSDMap文件是Monitor維護(hù)的OSDMap的持久化形式,相當(dāng)于log的作用。在Monitor 端,從epoch為1到當(dāng)前epoch的所有的集群Map信息都會在Monitor的KeyValueDB中存儲。
七、與OSDMap相關(guān)的參數(shù)配置
mon_osd_cache_size
-
描述:OSDMaps的緩存大小,不依賴于底層的存儲緩存
-
類型:int
-
默認(rèn)值:10
mon_osd_allow_primary_temp
-
描述:允許在OSDMap中設(shè)置primary_temp
-
類型:bool
-
默認(rèn)值:false
mon_osd_prime_pg_temp
-
描述:prime osdmap with pg mapping changes
-
類型:bool
-
默認(rèn)值:true
mon_min_osdmap_epochs
-
描述:
-
類型:int
-
默認(rèn)值:500
osd_map_message_max
-
描述:每個MOSDMap消息最大的map數(shù)
-
類型:int
-
默認(rèn)值:100
osd_map_dedup
-
描述:允許刪除OSDMap里的重復(fù)項(xiàng)
-
類型:bool
-
默認(rèn)值:true
osd_map_cache_size
-
描述:緩存的OSDMap個數(shù)
-
類型:int
-
默認(rèn)值:200
八、疑點(diǎn)
1.is_up_acting_osd_shard
messenger 建立連接的時(shí)候bind的socket addr和socket? port的指定
public_messenger->bind() | -- | AsyncMessenger::bind() | -- | -- | Processor::bind()AsyncMessenger::bindv() {AsyncMessenger::bindv(){std::set<int> avoid_ports;entity_addrvec_t bound_addrs;unsigned i = 0;for (auto &&p : processors){int r = p->bind(bind_addrs, avoid_ports, &bound_addrs);}} }int Processor::bind(const entity_addrvec_t &bind_addrs,
const std::set<int>& avoid_ports,
entity_addrvec_t* bound_addrs)
{
……
//try a range of ports 在給定的端口范圍內(nèi),找出一個沒有被本進(jìn)程使用過的端口,且能綁定上的for (int port = msgr->cct->_conf->ms_bind_port_min;port <= msgr->cct->_conf->ms_bind_port_max;port++) {if (avoid_ports.count(port))continue;
listen_addr.set_port(port);//找到一個端口,接下來嘗試bind
worker->center.submit_to(
worker->center.get_id(),
[this, k, &listen_addr, &opts, &r]() {
r = worker->listen(listen_addr, k, opts, &listen_sockets[k]);
}, false);
if (r == 0)
break;
}
if (r < 0) {
lderr(msgr->cct) << __func__ << " unable to bind to " << listen_addr
<< " on any port in range "
<< msgr->cct->_conf->ms_bind_port_min
<< "-" << msgr->cct->_conf->ms_bind_port_max << ": "
<< cpp_strerror(r) << dendl;
listen_addr.set_port(0); // Clear port before retry, otherwise we shall fail again.
continue;
}
ldout(msgr->cct, 10) << __func__ << " bound on random port "
}
bind完成之后,將傳入?yún)?shù)bind_addrs里的對應(yīng)的v的port設(shè)置為 (ms_bind_port_min到ms_bind_port_max之間的某端口號,然后交給 worker->listen,如果listen成功就返回,傳入?yún)?shù)entity_addrvec_t bind_addrs里的對應(yīng)的v的port的值,就為剛才設(shè)置的值,傳出去了
int Processor::bind(const entity_addrvec_t &bind_addrs,……)
auto& listen_addr = bound_addrs->v[k];
listen_addr.set_port(port);
r = worker->listen(listen_addr, k, opts, &listen_sockets[k]);
int RDMAWorker::listen(entity_addr_t &sa, unsigned addr_slot,
const SocketOptions &opt,ServerSocket *sock)
p = new RDMAServerSocketImpl(cct, ib, dispatcher, this, sa, addr_slot);
int r = p->listen(sa, opt);
port 在 entity_addr_t 結(jié)構(gòu)體里
/*
?* an entity's network address.
?* includes a random value that prevents it from being reused.
?* thus identifies a particular process instance.
?* ipv4 for now.
?*/
struct entity_addr_t {
? typedef enum {
? ? TYPE_NONE = 0,
? ? TYPE_LEGACY = 1, ?///< legacy msgr1 protocol (ceph jewel and older)
? ? TYPE_MSGR2 = 2, ? ///< msgr2 protocol (new in ceph kraken)
? ? TYPE_ANY = 3, ?///< ambiguous
? } type_t;
? static const type_t TYPE_DEFAULT = TYPE_MSGR2;
? static std::string_view get_type_name(int t) {
? ? switch (t) {
? ? case TYPE_NONE: return "none";
? ? case TYPE_LEGACY: return "v1";
? ? case TYPE_MSGR2: return "v2";
? ? case TYPE_ANY: return "any";
? ? default: return "???";
? ? }
? };
? __u32 type;
? __u32 nonce;
? union {
? ? sockaddr sa;
? ? sockaddr_in sin;
? ? sockaddr_in6 sin6;
? } u;
? entity_addr_t() : type(0), nonce(0) {
? ? memset(&u, 0, sizeof(u));
? }
? entity_addr_t(__u32 _type, __u32 _nonce) : type(_type), nonce(_nonce) {
? ? memset(&u, 0, sizeof(u));
? }
? explicit entity_addr_t(const ceph_entity_addr &o) {
? ? type = o.type;
? ? nonce = o.nonce;
? ? memcpy(&u, &o.in_addr, sizeof(u));
#if !defined(__FreeBSD__)
? ? u.sa.sa_family = ntohs(u.sa.sa_family);
#endif
? }
? uint32_t get_type() const { return type; }
? void set_type(uint32_t t) { type = t; }
? bool is_legacy() const { return type == TYPE_LEGACY; }
? bool is_msgr2() const { return type == TYPE_MSGR2; }
? bool is_any() const { return type == TYPE_ANY; }
? __u32 get_nonce() const { return nonce; }
? void set_nonce(__u32 n) { nonce = n; }
? int get_family() const {
? ? return u.sa.sa_family;
? }
? void set_family(int f) {
? ? u.sa.sa_family = f;
? }
? bool is_ipv4() const {
? ? return u.sa.sa_family == AF_INET;
? }
? bool is_ipv6() const {
? ? return u.sa.sa_family == AF_INET6;
? }
? sockaddr_in &in4_addr() {
? ? return u.sin;
? }
? const sockaddr_in &in4_addr() const{
? ? return u.sin;
? }
? sockaddr_in6 &in6_addr(){
? ? return u.sin6;
? }
? const sockaddr_in6 &in6_addr() const{
? ? return u.sin6;
? }
? const sockaddr *get_sockaddr() const {
? ? return &u.sa;
? }
? size_t get_sockaddr_len() const {
? ? switch (u.sa.sa_family) {
? ? case AF_INET:
? ? ? return sizeof(u.sin);
? ? case AF_INET6:
? ? ? return sizeof(u.sin6);
? ? }
? ? return sizeof(u);
? }
? bool set_sockaddr(const struct sockaddr *sa)
? {
? ? switch (sa->sa_family) {
? ? case AF_INET:
? ? ? // pre-zero, since we're only copying a portion of the source
? ? ? memset(&u, 0, sizeof(u));
? ? ? memcpy(&u.sin, sa, sizeof(u.sin));
? ? ? break;
? ? case AF_INET6:
? ? ? // pre-zero, since we're only copying a portion of the source
? ? ? memset(&u, 0, sizeof(u));
? ? ? memcpy(&u.sin6, sa, sizeof(u.sin6));
? ? ? break;
? ? case AF_UNSPEC:
? ? ? memset(&u, 0, sizeof(u));
? ? ? break;
? ? default:
? ? ? return false;
? ? }
? ? return true;
? }
? sockaddr_storage get_sockaddr_storage() const {
? ? sockaddr_storage ss;
? ? memcpy(&ss, &u, sizeof(u));
? ? memset((char*)&ss + sizeof(u), 0, sizeof(ss) - sizeof(u));
? ? return ss;
? }
? void set_in4_quad(int pos, int val) {
? ? u.sin.sin_family = AF_INET;
? ? unsigned char *ipq = (unsigned char*)&u.sin.sin_addr.s_addr;
? ? ipq[pos] = val;
? } void set_port(int port) {
? ? switch (u.sa.sa_family) {
? ? case AF_INET: u.sin.sin_port = htons(port);
? ? ? break;
? ? case AF_INET6: ? u.sin6.sin6_port = htons(port);
? ? ? break;
? ? default:
? ? ? ceph_abort();
? ? }
? }
? int get_port() const {
? ? switch (u.sa.sa_family) {
? ? case AF_INET:
? ? ? return ntohs(u.sin.sin_port);
? ? case AF_INET6:
? ? ? return ntohs(u.sin6.sin6_port);
? ? }
? ? return 0;
? }
? operator ceph_entity_addr() const {
? ? ceph_entity_addr a;
? ? a.type = 0;
? ? a.nonce = nonce;
? ? a.in_addr = get_sockaddr_storage();
#if !defined(__FreeBSD__)
? ? a.in_addr.ss_family = htons(a.in_addr.ss_family);
#endif
? ? return a;
? }
? bool probably_equals(const entity_addr_t &o) const {
? ? if (get_port() != o.get_port())
? ? ? return false;
? ? if (get_nonce() != o.get_nonce())
? ? ? return false;
? ? if (is_blank_ip() || o.is_blank_ip())
? ? ? return true;
? ? if (memcmp(&u, &o.u, sizeof(u)) == 0)
? ? ? return true;
? ? return false;
? }
? bool is_same_host(const entity_addr_t &o) const {
? ? if (u.sa.sa_family != o.u.sa.sa_family)
? ? ? return false;
? ? if (u.sa.sa_family == AF_INET)
? ? ? return u.sin.sin_addr.s_addr == o.u.sin.sin_addr.s_addr;
? ? if (u.sa.sa_family == AF_INET6)
? ? ? return memcmp(u.sin6.sin6_addr.s6_addr,
?? ??? ? ? ?o.u.sin6.sin6_addr.s6_addr,
?? ??? ? ? ?sizeof(u.sin6.sin6_addr.s6_addr)) == 0;
? ? return false;
? }
? bool is_blank_ip() const {
? ? switch (u.sa.sa_family) {
? ? case AF_INET:
? ? ? return u.sin.sin_addr.s_addr == INADDR_ANY;
? ? case AF_INET6:
? ? ? return memcmp(&u.sin6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0;
? ? default:
? ? ? return true;
? ? }
? }
? bool is_ip() const {
? ? switch (u.sa.sa_family) {
? ? case AF_INET:
? ? case AF_INET6:
? ? ? return true;
? ? default:
? ? ? return false;
? ? }
? }
? std::string ip_only_to_str() const;
? std::string get_legacy_str() const {
? ? std::ostringstream ss;
? ? ss << get_sockaddr() << "/" << get_nonce();
? ? return ss.str();
? }
? bool parse(const std::string_view s);
? bool parse(const char *s, const char **end = 0, int type=0);
? void decode_legacy_addr_after_marker(ceph::buffer::list::const_iterator& bl)
? {
? ? using ceph::decode;
? ? __u8 marker;
? ? __u16 rest;
? ? decode(marker, bl);
? ? decode(rest, bl);
? ? decode(nonce, bl);
? ? sockaddr_storage ss;
? ? decode(ss, bl);
? ? set_sockaddr((sockaddr*)&ss);
? ? if (get_family() == AF_UNSPEC) {
? ? ? type = TYPE_NONE;
? ? } else {
? ? ? type = TYPE_LEGACY;
? ? }
? }
? // Right now, these only deal with sockaddr_storage that have only family and content.
? // Apparently on BSD there is also an ss_len that we need to handle; this requires
? // broader study
? void encode(ceph::buffer::list& bl, uint64_t features) const {
? ? using ceph::encode;
? ? if ((features & CEPH_FEATURE_MSG_ADDR2) == 0) {
? ? ? encode((__u32)0, bl);
? ? ? encode(nonce, bl);
? ? ? sockaddr_storage ss = get_sockaddr_storage();
? ? ? encode(ss, bl);
? ? ? return;
? ? }
? ? encode((__u8)1, bl);
? ? ENCODE_START(1, 1, bl);
? ? if (HAVE_FEATURE(features, SERVER_NAUTILUS)) {
? ? ? encode(type, bl);
? ? } else {
? ? ? // map any -> legacy for old clients. ?this is primary for the benefit
? ? ? // of OSDMap's blocklist, but is reasonable in general since any: is
? ? ? // meaningless for pre-nautilus clients or daemons.
? ? ? auto t = type;
? ? ? if (t == TYPE_ANY) {
?? ?t = TYPE_LEGACY;
? ? ? }
? ? ? encode(t, bl);
? ? }
? ? encode(nonce, bl);
? ? __u32 elen = get_sockaddr_len();
#if (__FreeBSD__) || defined(__APPLE__)
? ? ? elen -= sizeof(u.sa.sa_len);
#endif
? ? encode(elen, bl);
? ? if (elen) {
? ? ? uint16_t ss_family = u.sa.sa_family;
? ? ? encode(ss_family, bl);
? ? ? elen -= sizeof(u.sa.sa_family);
? ? ? bl.append(u.sa.sa_data, elen);
? ? }
? ? ENCODE_FINISH(bl);
? }
? void decode(ceph::buffer::list::const_iterator& bl) {
? ? using ceph::decode;
? ? __u8 marker;
? ? decode(marker, bl);
? ? if (marker == 0) {
? ? ? decode_legacy_addr_after_marker(bl);
? ? ? return;
? ? }
? ? if (marker != 1)
? ? ? throw ceph::buffer::malformed_input("entity_addr_t marker != 1");
? ? DECODE_START(1, bl);
? ? decode(type, bl);
? ? decode(nonce, bl);
? ? __u32 elen;
? ? decode(elen, bl);
? ? if (elen) {
#if defined(__FreeBSD__) || defined(__APPLE__)
? ? ? u.sa.sa_len = 0;
#endif
? ? ? uint16_t ss_family;
? ? ? if (elen < sizeof(ss_family)) {
?? ?throw ceph::buffer::malformed_input("elen smaller than family len");
? ? ? }
? ? ? decode(ss_family, bl);
? ? ? u.sa.sa_family = ss_family;
? ? ? elen -= sizeof(ss_family);
? ? ? if (elen > get_sockaddr_len() - sizeof(u.sa.sa_family)) {
?? ?throw ceph::buffer::malformed_input("elen exceeds sockaddr len");
? ? ? }
? ? ? bl.copy(elen, u.sa.sa_data);
? ? }
? ? DECODE_FINISH(bl);
? }
? void dump(ceph::Formatter *f) const;
? static void generate_test_instances(std::list<entity_addr_t*>& o);
}
總結(jié)
以上是生活随笔為你收集整理的【ceph】Ceph 存储中 PGMap、OSDMap 和xxMap的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机系统字体变大,手把手教你如何调整电
- 下一篇: cacheput注解 用法_以注解的方式