MongoDB oplog (相似於 MySQL binlog) 記錄數據庫的全部修改操做,除了用於主備同步;oplog 還能玩出不少花樣,好比git
總的來講,MongoDB 能夠經過 oplog 來跟生態對接,來實現數據的同步、遷移、恢復等能力。而在構建這些能力的時候,有一個通用的需求,就是工具或者應用須要有不斷拉取 oplog 的能力;這個過程一般是github
那麼問題來了,因爲 MongoDB oplog 自己沒有索引的,每次定位 oplog 的起點都須要進行全表掃描麼?mongodb
{ "ts" : Timestamp(1563950955, 2), "t" : NumberLong(1), "h" : NumberLong("-5936505825938726695"), "v" : 2, "op" : "i", "ns" : "test.coll", "ui" : UUID("020b51b7-15c2-4525-9c35-cd50f4db100d"), "wall" : ISODate("2019-07-24T06:49:15.903Z"), "o" : { "_id" : ObjectId("5d37ff6b204906ac17e28740"), "x" : 0 } } { "ts" : Timestamp(1563950955, 3), "t" : NumberLong(1), "h" : NumberLong("-1206874032147642463"), "v" : 2, "op" : "i", "ns" : "test.coll", "ui" : UUID("020b51b7-15c2-4525-9c35-cd50f4db100d"), "wall" : ISODate("2019-07-24T06:49:15.903Z"), "o" : { "_id" : ObjectId("5d37ff6b204906ac17e28741"), "x" : 1 } } { "ts" : Timestamp(1563950955, 4), "t" : NumberLong(1), "h" : NumberLong("1059466947856398068"), "v" : 2, "op" : "i", "ns" : "test.coll", "ui" : UUID("020b51b7-15c2-4525-9c35-cd50f4db100d"), "wall" : ISODate("2019-07-24T06:49:15.913Z"), "o" : { "_id" : ObjectId("5d37ff6b204906ac17e28742"), "x" : 2 } }
上面是 MongoDB oplog 的示例,oplog MongoDB 也是一個集合,但與普通集合不同數據庫
咱們在拉取 oplog 時,第一次從頭開始拉取,而後每次拉取使用完,會記錄最後一條 oplog 的ts字段;若是應用發生重啓,這時須要根據上次拉取的 ts 字段,先找到拉取的起點,而後繼續遍歷。app
注:如下實現針對 WiredTiger 存儲引擎,須要 MongoDB 3.0+ 版本才能支持工具
若是 MongoDB 底層使用的是 WiredTiger 存儲引擎,在存儲 oplog 時,實際上作過優化。MongoDB 會將 ts 字段做爲 key,oplog 的內容做爲 value,將key-value 存儲到 WiredTiger 引擎裏,WiredTiger 默認配置使用 btree 存儲,因此 oplog 的數據在 WT 裏實際上也是按 ts 字段順序存儲的,既然是順序存儲,那就有二分查找優化的空間。優化
MongoDB find 命令提供了一個選項,專門用於優化 oplog 定位。ui
大體意思是,若是你find的集合是oplog,查找條件是針對 ts 字段的 gte
、gt
、eq
,那麼 MongoDB 字段會進行優化,經過二分查找快速定位到起點; 備節點同步拉取oplog時,實際上就帶了這個選項,這樣備節點每次重啓,都能根據上次同步的位點,快速找到同步起點,而後持續保持同步。阿里雲
因爲諮詢問題的同窗對內部實現感興趣,這裏簡單的把重點列出來,要深入理解,仍是得深刻擼細節。url
// src/monogo/db/query/get_executor.cpp StatusWith<unique_ptr<PlanExecutor>> getExecutorFind(OperationContext* txn, Collection* collection, const NamespaceString& nss, unique_ptr<CanonicalQuery> canonicalQuery, PlanExecutor::YieldPolicy yieldPolicy) { // 構建 find 執行計劃時,若是發現有 oplogReplay 選項,則走優化路徑 if (NULL != collection && canonicalQuery->getQueryRequest().isOplogReplay()) { return getOplogStartHack(txn, collection, std::move(canonicalQuery)); } ... return getExecutor( txn, collection, std::move(canonicalQuery), PlanExecutor::YIELD_AUTO, options); }
StatusWith<unique_ptr<PlanExecutor>> getOplogStartHack(OperationContext* txn, Collection* collection, unique_ptr<CanonicalQuery> cq) { // See if the RecordStore supports the oplogStartHack // 若是底層引擎支持(WT支持,mmapv1不支持),根據查詢的ts,找到 startLoc const BSONElement tsElem = extractOplogTsOptime(tsExpr); if (tsElem.type() == bsonTimestamp) { StatusWith<RecordId> goal = oploghack::keyForOptime(tsElem.timestamp()); if (goal.isOK()) { // 最終調用 src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp::oplogStartHack startLoc = collection->getRecordStore()->oplogStartHack(txn, goal.getValue()); } } // Build our collection scan... // 構建全表掃描參數時,帶上 startLoc,真正執行是會快速定位到這個點 CollectionScanParams params; params.collection = collection; params.start = *startLoc; params.direction = CollectionScanParams::FORWARD; params.tailable = cq->getQueryRequest().isTailable(); }
本文做者:張友東
本文爲雲棲社區原創內容,未經容許不得轉載。