ceph PGLog處理流程

ceph的PGLog是由PG來維護,記錄了該PG的全部操做,其做用相似於數據庫裏的undo log。PGLog一般只保存近千條的操做記錄(默認是3000條),可是當PG處於降級狀態時,就會保存更多的日誌(默認是10000條),這樣就能夠在故障的PG重現上線後用來恢復PG的數據。本文主要從PGLog的格式、存儲方式、如何參與恢復來解析PGLog。數據庫

1.PGLog的格式

ceph使用版本控制的方式來標記一個PG內的每一次更新,每一個版本包括一個(epoch,version)來組成:其中epoch是osdmap的版本,每當有OSD狀態變化如增長刪除等時,epoch就遞增;version是PG內每次更新操做的版本號,遞增的,由PG內的Primary OSD進行分配的。緩存

PGLog在代碼實現中有3個主要的數據結構來維護:pg_info_t,pg_log_t,pg_log_entry_t。三者的關係示意圖以下。從結構上能夠得知,PGLog裏只有對象更新操做相關的內容,沒有具體的數據以及偏移大小等,因此後續以PGLog來進行恢復時都是按照整個對象來進行恢復的(默認對象大小是4MB)。
pglog

其中:數據結構

  • last_complete:在該指針以前的版本都已經在全部的OSD上完成更新(只表示內存更新完成);
  • last_update:PG內最近一次更新的對象的版本,尚未在全部OSD上完成更新,在last_update與last_complete之間的操做表示該操做已在部分OSD上完成可是尚未所有完成;
  • log_tail:指向pg log最老的那條記錄;
  • head:最新的pg log記錄;
  • tail:指向最老的pg log記錄的前一個;
  • log:存放實際的pglog記錄的list;

2.PGLog的存儲方式

瞭解了PGLog的格式以後,咱們就來分析一下PGLog的存儲方式。在ceph的實現裏,對於寫I/O的處理,都是先封裝成一個transaction,而後將這個transaction寫到journal裏,在journal寫完成後,觸發回調流程,通過多個線程及回調的處理後再進行寫數據到buffer cache的操做,從而完成整個寫journal和寫本地緩存的流程(具體的流程在《OSD讀寫處理流程》一文中有詳細描述)。app

整體來講,PGLog也是封裝到transaction中,在寫journal的時候一塊兒寫到日誌盤上,最後在寫本地緩存的時候遍歷transaction裏的內容,將PGLog相關的東西寫到Leveldb裏,從而完成該OSD上PGLog的更新操做。異步

2.1 PGLog更新到journal

2.1.1 寫I/O序列化到transaction

在《OSD讀寫流程》裏描述了主OSD上的讀寫處理流程,這裏就不作說明。在ReplicatedPG::do_osd_ops函數里根據類型CEPH_OSD_OP_WRITE就會進行封裝寫I/O到transaction的操做(即將要寫的數據encode到ObjectStore::Transaction::tbl裏,這是個bufferlist,encode時都先將op編碼進去,這樣後續在處理時就能夠根據op來操做。注意這裏的encode其實就是序列化操做)。函數

這個transaction通過的過程以下:ui

1
2
ReplicatedPG::OpContext::op_t  –>  PGBackend::PGTransaction::write(即t->write) –>  RPGTransaction::write  –> ObjectStore::Transaction::write(encode到ObjectStore::Transaction::tbl) 
後面調用ReplicatedBackend::submit_transaction時傳入的PGTransaction *_t就是上面這個,經過轉換成RPGTransaction *t,而後這個函數裏用到的ObjectStore::Transaction *op_t就是對應到RPGTransaction裏的ObjectStore::Transaction *t。

 

2.1.2 PGLog序列化到transaction

  • 在ReplicatedPG::prepare_transaction裏調用ReplicatedPG::finish_ctx,而後在finish_ctx函數裏就會調用ctx->log.push_back就會構造pg_log_entry_t插入到vector log裏;
  • 在ReplicatedBackend::submit_transaction裏調用parent->log_operation將PGLog序列化到transaction裏。在PG::append_log裏將PGLog相關信息序列化到transaction裏。
  • 主要序列化到transaction裏的內容包括:pg_info_t,pg_log_entry_t,這兩種數據結構都是以map的形式encode到transaction的bufferlist裏。其中不一樣的map的value對應的就是pg_info和pg_log_entry的bufferlist。而map的key就是epoch+version構成的字符串」epoch.version」。另外須要注意的是這些map是附帶上op和oid做爲對象的omap(Object的屬性會利用文件的xattr屬性存取,由於有些文件系統對xattr的長度有限制,所以超出長度的Metadata會被存儲在DBObjectMap裏。而Object的omap則直接利用DBObjectMap實現。)來序列化到transaction裏;

2.1.3 Transaction裏的內容

從上面的分析得知,寫I/O和PGLog都會序列化到transaction裏的bufferlist裏,這裏就對這個bufferlist裏的主要內容以圖的形式展現出來。transaction的bufflist裏就是按照操做類型op來序列化不一樣的內容,如OP_WRITE表示寫I/O,而OP_OMAPSETKEYS就表示設置對象的omap,其中的attrset就是一個kv的map。 注意這裏面的oid,對於pglog來講,每一個pg在建立的時候就會生成一個logoid,會加上pglog構造的一個對象,對於pginfo來講,是pginfo_構造的一個對象,而對於真正的數據對象來講,attrset就是其屬性。
trans編碼

2.1.4 Trim Log

前面說到PGLog的記錄數是有限制的,正常狀況是默認是3000條(由參數osd_min_pg_log_entries控制),PG降級狀況下默認增長到10000條(由參數osd_max_pg_log_entries控制)。當達到限制時,就會trim log進行截斷。spa

在ReplicatedPG::execute_ctx裏調用ReplicatedPG::calc_trim_to來進行計算。計算的時候從log的tail(tail指向最老的記錄)開始,須要trim的條數=log.head-log.tail-max_entries。可是trim的時候須要考慮到min_last_complete_ondisk(這個表示各個副本上last_complete的最小版本,是主osd在收到3副本都完成時再進行計算的,也就是計算last_complete_ondisk和其餘副本osd上的last_complete_ondisk–即peer_last_complete_ondisk的最小值獲得min_last_complete_ondisk),也就是說trim的時候不能超過min_last_complete_ondisk,由於超過了的也trim掉的話就會致使沒有更新到磁盤上的pg log丟失。因此說可能存在某個時候pglog的記錄數超過max_entries。
trim_log
在ReplicatedPG::log_operation裏的trim_to就是pg_trim_to,trim_rollback_to就是min_last_complete_on_disk。log_operation裏調用pg_log.trim(&handler, trim_to, info)進行trim,會將須要trim的key加入到PGLog::trimmed這個set裏。而後在_write_log裏將trimmed裏插入到to_remove裏,最後在調用t.omap_rmkeys序列化到transaction的bufferlist裏。線程

2.1.5 PGLog寫到journal盤

PGLog寫到journal盤上就是寫journal同樣的流程,具體以下:

  • 在ReplicatedBackend::submit_transaction調用log_operation將PGLog序列化到transaction裏,而後調用queue_transaction將這個transaction傳到後續進行處理;
  • 調用到了FileStore::queue_transactions裏,就將list構形成一個FileStore::Op,對應的list放到FileStore::Op::tls裏;
  • 接着在JournalingObjectStore::_op_journal_transactions函數裏遍歷list& tls,將ObjectStore::Transaction encode到一個bufferlist裏(記爲tbl);
  • 而後FileJournal::submit_entry裏將bufferlist構形成write_item放到writeq;
  • 接着在FileJournal::write_thread_entry會從writeq裏取出write_item,放到另一個bufferlist裏;
  • 最後調用do_aio_write將bufferlist的內容異步寫到磁盤上(也就是寫journal);

2.2 PGLog寫入leveldb

在《OSD讀寫流程》裏描述到是在FileStore::_do_op裏進行寫數據到本地緩存的操做。將pglog寫入到leveldb裏的操做也是從這裏出發的,會根據不一樣的op類型來進行不一樣的操做。
好比OP_OMAP_SETKEYS(PGLog寫入leveldb就是根據這個key):

1
FileStore::_do_op --> FileStore::_do_transactions --> FileStore::_do_transaction 根據不一樣的Transaction類型來進行不一樣的操做 --> case Transaction::OP_OMAP_SETKEYS --> FileStore::_omap_setkeys --> object_map->rm_keys,即DBObjectMap::set_keys --> KeyValueDB::TransactionImpl::set,遍歷map<string, bufferlist>,而後調用set(prefix, it->first, it->second),即調用到LevelDBStore::LevelDBTransactionImpl::set --> 最後調用db->submit_transaction提交事務寫到盤上

 

再好比以OP_OMAP_RMKEYS(trim pglog的時候就是用到了這個key)爲例:

1
FileStore::_do_op --> FileStore::_do_transactions --> FileStore::_do_transaction 根據不一樣的Transaction類型來進行不一樣的操做 --> case Transaction::OP_OMAP_RMKEYS --> FileStore::_omap_rmkeys --> object_map->rm_keys,後面就是調用到LevelDB裏的rm_keys去刪除keys。

 

PGLog封裝到transaction裏面和journal一塊兒寫到盤上的好處:若是osd異常崩潰時,journal寫完成了,可是數據有可能沒有寫到磁盤上,相應的pg log也沒有寫到leveldb裏,這樣在osd再啓動起來時,就會進行journal replay,這樣從journal裏就能讀出完整的transaction,而後再進行事務的處理,也就是將數據寫到盤上,pglog寫到leveldb裏。

3. PGLog如何參與恢復

PGLog參與恢復主要體如今ceph進行peering的時候創建missing列表來標記過期數據,以便於進行對這些數據進行修復。
故障的OSD從新上線後,PG就會標記爲peering狀態並暫停處理請求。

  • 對於故障OSD所擁有的Primary PG 
    • 它做爲這部分數據」權責」主體,須要發送查詢 PG 元數據請求給全部屬於該 PG 的 Replicate 角色節點;
    • 該 PG 的 Replicate 角色節點實際上在故障 OSD 下線時期間成爲了 Primary 角色並維護了「權威」的 PGLog,該 PG 在獲得故障 OSD 的 Primary PG 的查詢請求後會發送迴應;
    • Primary PG 經過對比 Replicate PG 發送的元數據和 PG 版本信息後發現處於落後狀態,所以它會合並獲得的 PGLog並創建「權威」 PGLog,同時會創建 missing 列表來標記過期數據;
    • Primary PG 在完成「權威」 PGLog 的創建後就能夠標誌本身處於 Active 狀態;
  • 對於故障OSD所擁有的Replicate PG 
    • 這時上線後故障 OSD 的 Replicate PG 會獲得 Primary PG 的查詢請求,發送本身這份「過期」的元數據和 PGLog;
    • Primary PG 對比數據後發現該 PG 落後而且過期,比經過 PGLog 創建了 missing 列表;
    • Primary PG 標記本身處於 Active 狀態;
      Peering過程當中涉及到PGLog(pg_info和pg_log)的步驟主要包括:
  • GetInfo : PG的Primary OSD經過發送消息獲取各個Replicate OSD的pg_info信息。在收到各個Replicate OSD的pg_info後,會調用PG::proc_replica_info處理副本OSD的pg_info,在這裏面會調用info.history.merge合併Replicate OSD發過來的pg_info信息,合併的原則就是更新爲最新的字段(好比last_epoch_started和last_epoch_clean都變成最新的);
  • GetLog:根據pg_info的比較,選擇一個擁有權威日誌的OSD(auth_log_shard) , 若是Primary OSD不是擁有權威日誌的OSD,就去該OSD上獲取權威日誌;
    • 選取擁有權威日誌的OSD時,遵循3個原則(在find_best_info裏):
      • Prefer newer last_update
      • Prefer longer tail if it brings another info into contiguity
      • Prefer current primary
    • 也就是說對比各個OSD的pg_info_t,誰的last_update大,就選誰,若是last_update都同樣,則誰的log_tail小,就選誰,若是log_tail也同樣,就選當前的Primary OSD;
    • 若是Primary OSD不是擁有權威日誌的OSD,則須要去擁有權威日誌的osd上去拉取權威日誌,收到權威日誌後,會調用proc_master_log將權威日誌合併到本地pg log;
    • 在merge 權威log到本地pg log的過程當中,會將merge的pg_log_entry_t對應的oid和eversion放到missing列表裏,這個missing列表裏的對象就是Primary OSD所缺失的對象,後續在recovery的時候須要從其餘osd pull的。
      merge
  • GetMissing:拉取其它Replicate OSD 的pg log(或者部分獲取,或者所有獲取FULL_LOG) , 經過本地的auth log對比,調用proc_replica_log處理日誌,會將Replicate OSD裏缺失的對象放到peer_missing列表裏,以用於後續recovery過程的依據;注意:其實是在PG:activate裏更新peer_missing列表的,在proc_replica_log處理的只是從replica傳過來它本地的missing(就是replica重啓後根據自身的last_update和last_complete構造的missing列表),通常狀況下這個missing列表是空
相關文章
相關標籤/搜索