ceph的PGLog是由PG來維護,記錄了該PG的全部操做,其做用相似於數據庫裏的undo log。PGLog一般只保存近千條的操做記錄(默認是3000條),可是當PG處於降級狀態時,就會保存更多的日誌(默認是10000條),這樣就能夠在故障的PG重現上線後用來恢復PG的數據。本文主要從PGLog的格式、存儲方式、如何參與恢復來解析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的格式以後,咱們就來分析一下PGLog的存儲方式。在ceph的實現裏,對於寫I/O的處理,都是先封裝成一個transaction,而後將這個transaction寫到journal裏,在journal寫完成後,觸發回調流程,通過多個線程及回調的處理後再進行寫數據到buffer cache的操做,從而完成整個寫journal和寫本地緩存的流程(具體的流程在《OSD讀寫處理流程》一文中有詳細描述)。app
整體來講,PGLog也是封裝到transaction中,在寫journal的時候一塊兒寫到日誌盤上,最後在寫本地緩存的時候遍歷transaction裏的內容,將PGLog相關的東西寫到Leveldb裏,從而完成該OSD上PGLog的更新操做。異步
在《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。 |
從上面的分析得知,寫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就是其屬性。
編碼
前面說到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。
在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裏。線程
PGLog寫到journal盤上就是寫journal同樣的流程,具體以下:
在《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裏。
PGLog參與恢復主要體如今ceph進行peering的時候創建missing列表來標記過期數據,以便於進行對這些數據進行修復。
故障的OSD從新上線後,PG就會標記爲peering狀態並暫停處理請求。