MongoDB writeConcern和readConcern原理

FeelTouch發表於2019-03-20

目錄

 

readConcern

readConcern作用

readConcern原理

readConcern須知

readPreference

writeConcern

writeConcern選項

{w: "majority"}解析


readConcern

readConcern作用

MongoDB 可以利用readConcern 來靈活的定製讀策略,決定讀取資料時,能讀到什麼樣的資料。包含2個選項:

  • local 能讀取任意資料,這個是預設設定
  • majority 只能讀取到『成功寫入到大多數節點的資料』

readConcern 的初衷在於解決『髒讀』的問題,比如使用者從 MongoDB 的 primary 上讀取了某一條資料,但這條資料並沒有同步到大多數節點,然後 primary 就故障了,重新恢復後 這個primary 節點會將未同步到大多數節點的資料回滾掉,導致使用者讀到了『髒資料』。

當指定 readConcern 級別為 majority 時,能保證使用者讀到的資料『已經寫入到大多數節點』,而這樣的資料肯定不會發生回滾,避免了髒讀的問題。

需要注意的是,readConcern 能保證讀到的資料『不會發生回滾』,但並不能保證讀到的資料是最新的,

有使用者誤以為,readConcern 指定為 majority 時,客戶端會從大多數的節點讀取資料,然後返回最新的資料。

實際上並不是這樣,無論何種級別的 readConcern,客戶端都只會從『某一個確定的節點』(具體是哪個節點由 readPreference 決定)讀取資料,該節點根據自己看到的同步狀態檢視,只會返回已經同步到大多數節點的資料。

readConcern原理

MongoDB 要支援 majority 的 readConcern 級別,必須設定replication.enableMajorityReadConcern引數,加上這個引數後,MongoDB 會起一個單獨的snapshot 執行緒,會週期性的對當前的資料集進行 snapshot,並記錄 snapshot 時最新 oplog的時間戳,得到一個對映表。

最新 oplog 時間戳 snapshot 狀態
t0 snapshot0 committed
t1 snapshot1 uncommitted
t2 snapshot2 uncommitted
t3 snapshot3 uncommitted

只有確保 oplog 已經同步到大多數節點時,對應的 snapshot 才會標記為 commmited,使用者讀取時,從最新的 commited 狀態的 snapshot 讀取資料,就能保證讀到的資料一定已經同步到的大多數節點。

關鍵的問題就是如何確定『oplog 已經同步到大多數節點』?

primary 節點

secondary 節點在 自身oplog發生變化時,會通過 replSetUpdatePosition 命令來將 oplog 進度立即通知給 primary,另外心跳的訊息裡也會包含最新 oplog 的資訊;通過上述方式,primary 節點能很快知道 oplog 同步情況,知道『最新一條已經同步到大多數節點的 oplog』,並更新 snapshot 的狀態。比如當t2已經寫入到大多資料節點時,snapshot1、snapshot2都可以更新為 commited 狀態。(不必要的 snapshot也會定期被清理掉)

secondary 節點

secondary 節點拉取 oplog 時,primary 節點會將『最新一條已經同步到大多數節點的 oplog』的資訊返回給 secondary 節點,secondary 節點通過這個oplog時間戳來更新自身的 snapshot 狀態。

readConcern須知

  • 目前 readConcern 主要用於跟 mongos 與 config server 的互動上
  • 使用 readConcern 需要配置replication.enableMajorityReadConcern選項
  • 只有支援 readCommited 隔離級別的儲存引擎才能支援 readConcern,比如 wiredtiger 引擎,而 mmapv1引擎則不能支援。

readPreference

MongoDB 控制讀策略,還有一個 readPreference 的設定,主要控制客戶端 Driver 從複製集的哪個節點讀取資料,這個特性可方便的實現讀寫分離、就近讀取等策略。

  • primary 只從 primary 節點讀資料,這個是預設設定
  • primaryPreferred 優先從 primary 讀取,primary 不可服務,從 secondary 讀
  • secondary 只從 scondary 節點讀資料
  • secondaryPreferred 優先從 secondary 讀取,沒有 secondary 成員時,從 primary 讀取
  • nearest 根據網路距離就近讀取

writeConcern

MongoDB支援客戶端靈活配置寫入策略(writeConcern),以滿足不同場景的需求。

db.collection.insert({x: 1}, {writeConcern: {w: 1}})

writeConcern選項

MongoDB支援的WriteConncern選項如下

  • w: <number>,資料寫入到number個節點才向用客戶端確認

    • {w: 0} 對客戶端的寫入不需要傳送任何確認,適用於效能要求高,但不關注正確性的場景
    • {w: 1} 預設的writeConcern,資料寫入到Primary就向客戶端傳送確認
    • {w: "majority"} 資料寫入到副本集大多數成員後向客戶端傳送確認,適用於對資料安全性要求比較高的場景,該選項會降低寫入效能
  • j: <boolean> ,寫入操作的journal持久化後才向客戶端確認

    • 預設為"{j: false},如果要求Primary寫入持久化了才向客戶端確認,則指定該選項為true
  • wtimeout: <millseconds>,寫入超時時間,僅w的值大於1時有效。

    • 當指定{w: }時,資料需要成功寫入number個節點才算成功,如果寫入過程中有節點故障,可能導致這個條件一直不能滿足,從而一直不能向客戶端傳送確認結果,針對這種情況,客戶端可設定wtimeout選項來指定超時時間,當寫入過程持續超過該時間仍未結束,則認為寫入失敗。

{w: "majority"}解析

{w: 1}、{j: true}等writeConcern選項很好理解,Primary等待條件滿足傳送確認;但{w: "majority"}則相對複雜些,需要確認資料成功寫入到大多數節點才算成功,而MongoDB的複製是通過Secondary不斷拉取oplog並重放來實現的,並不是Primary主動將寫入同步給Secondary,那麼Primary是如何確認資料已成功寫入到大多數節點的?

  1. Client向Primary發起請求,指定writeConcern為{w: "majority"},Primary收到請求,本地寫入並記錄寫請求到oplog,然後等待大多數節點都同步了這條/批oplog(Secondary應用完oplog會向主報告最新進度)。
  2. Secondary拉取到Primary上新寫入的oplog,本地重放並記錄oplog。為了讓Secondary能在第一時間內拉取到主上的oplog,find命令支援一個awaitData的選項,當find沒有任何符合條件的文件時,並不立即返回,而是等待最多maxTimeMS(預設為2s)時間看是否有新的符合條件的資料,如果有就返回;所以當新寫入oplog時,備立馬能獲取到新的oplog。
  3. Secondary上有單獨的執行緒,當oplog的最新時間戳發生更新時,就會向Primary傳送replSetUpdatePosition命令更新自己的oplog時間戳。
  4. 當Primary發現有足夠多的節點oplog時間戳已經滿足條件了,向客戶端傳送確認。

參考:阿里雲MongoDB

相關文章