jlzzjlzz亚洲乱熟在线播放

系統城裝機大師 - 唯一官網:www.farandoo.com!

當前位置:首頁 > 數據庫 > DB2 > 詳細頁面

MongoDB 事務,復制和分片的關系

時間:2020-08-31來源:www.farandoo.com作者:電腦系統城

1.前言

  •  MongoDB基于wiredTiger提供的泛化SI的功能,重構了readHistory(readMajority)的能力
  •  基于wiredTiger提供的AllCommittedTimestamp API,重構了前綴一致的主從復制(Prefix-Consistent-Replication)
  •  引入混合邏輯時鐘(HLC),每個節點(Mongos/Mongod)的邏輯時鐘維持在接近的值,基于此實現ChangeStream, 結合HLC與CLOCK-SI,實現分布式事務,HLC和泛化SI,CLOCK-SI兩篇Paper可以作為理解MongoDB的設計的理論參考(這里并沒有說MongoDB是Paper的實現)。

本文嘗試對Mongo的復制和分布式事務的原理進行描述,在必要的地方,對實現的正確性進行論證,希望能為MongoDB內核愛好者提供一些參考。

2.MongoDB副本集事務介紹

  •  MongoDB 副本集的事務
    •  MongoDB副本集的復制是基于raft協議,相比于Paxos,raft協議實現簡單,但是raft協議只支持single-master,對應的,MongoDB的副本集是主從架構,而且只有主節點支持寫入操作。MongoDB副本集的事務管理,包括沖突檢測,事務提交等關鍵操作,都只在主節點上完成。也就是說副本集的事務在事務管理方面,跟單節點邏輯基本一致。
    •  MongoDB的事務,仍然是實現了 ACID 四個特性, MongoDB使用 SI 作為事務的隔離級別。

3.SI的簡介

  •  SI,即SnapshotIsolation,中文稱為快照隔離,是一種mvcc的實現機制,它在1995年的A Critique of ANSI SQL Isolation Levels中被正式提出。因快照時間點的選取上的不同,又分為Conventional Si 和 Generalized SI。

CSI(Convensional SI)

  •  CSI 選取當前最新的系統快照作為事務的讀取快照
    •  就是在事務開始的時候,獲得當前db最新的snapshot,作為事務的讀取的snapshot,
    •  snapshot(Ti) = start(Ti)
    •  可以減少寫事務沖突發生的概率,并且提供讀事務讀取最新數據的能力
    •  一般我們說一個數據庫支持SI隔離級別,其實默認是說支持CSI。比如RocksDB支持的SI就是CSI,WiredTiger在3.0版本之前支持的SI也是CSI。

GSI(Generalized SI)

  •  GSI選擇歷史上的數據庫快照作為事務的讀取快照,因此CSI可以看作GSI的一個特例。
  •  在復制集的情況下,考慮 CSI, 對于主節點上的事務,每次事務的開始時間選取的系統 最新的 快照, 但是對于其他從節點來說, 并沒有 統一的 “最新的” 快照這個概念。泛化的快照實際上是基于快照觀測得到的,對于當前事務來說,我們通過選取合適的 更早時間的快照,可以讓 從節點上的事務正確且無延遲的執行。
  •  舉例如下:
    •  例如當前數據庫的狀態是, S={T1, T2, T3}, 現在要開始執行T4,
    •  如果我們知道T4要修改的值,在T3上沒有被修改, 那么我們在執行T4的時候, 就可以按照 T2 commit后的snashot進行讀取。
  •  如何選擇更早的時間點,需要滿足下面的規則,

  •  符號定義
  •  Ti: 事務i
  •  Xi: 被事務i修改過的X變量
  •  snapshot(Ti): 事務i的選取的快照時間
  •  start(Ti): 事務i的開始時間
  •  commit(Ti): 事務i的提交時間
  •  abort(Ti): the time when Ti is aborted.
  •  end(Ti): the time when Ti is committed or aborted.

公式解釋

讀規則

  •  G1.1, 如果變量X被本事務修改了值且讀取到了新的值, 那么 讀操作一定在寫操作后面;
  •  G1.2, 如果事務i讀取了事務j更新的變量的X, 那么一定不會有事務i更新X的操作,在事務i讀取了事務j更新的變量的X這個操作前面;
  •  G1.3, 事務j的提交時間早于事務i的快照時間;
  •  G1.4, 對于任意一個會更新變量X的事務k, 那么這個事務k一定滿足, 要么事務k的提交時間小于事務j, 要么這個事務k的提交時間大于事務i。

寫規則

  •  G2, 對于任意已經在提交歷史里的兩個事務,Ci, Cj, 那么一定可以保證當 事務j的commit時間戳在 事務i的觀測時間段內時(snapshot(Ti), commit(Ti)), 那么他們更新的變量交集一定為空。 

PCSI(PREFIX-CONSISTENT SNAPSHOT ISOLATION SI)

  • GSI 只是定義了一個范圍的range,都可以作為SI使用,并沒有定義具體應該選擇哪個SI。
  •  PCSI 是為了復制集而設計的。對于一個事務Ti 要開S節點開始運行, 那么 S節點將必須包含這個事務所需要的所有前置事務都必須運行且提交。
  •  相比較于GSI, PCSI的讀規則,額外增加了 P1.5 規則。   

  • SI的提交時間戳設置,依據 A Critique of ANSI SQL Isolation Levels 中的描述, 提交時間戳的設置應該是單調遞增的。新設置的時間戳,應該大于系統中已經存在的開始時間戳和提交時間戳。
  • SI 讀取時間戳的設置,必須保證比當前系統中正在運行的事務的最小的提交時間戳還要小, 因為一旦大于當前系統中正在運行事務的最小的提交時間戳,那么這個讀事務讀取到的數據就是未定義的, 取決于讀事務啟動的時間,而不是snapshot的時間,這違背了 一致性的要求。舉例如下
    •  當前已經完成的事務是T1,正在運行的事務是T2, 將要運行的讀事務是T3, 如果 T3的讀時間戳大于T2事務提交時間戳, 并且T2事務正在運行,等到T2事務執行完后。我們觀察這個 database,就會發現 他違背了GSI,

事務執行順序如下所示是:


 
  1. T1 commited and commitTs(1) -> T2 start -> T2 set commitTs(2) -> T3 start -> T3 set snapshotTs(3) -> T3 commit -> pointA -> T2 commit -> pointB 

那么可知, T3事務實際讀取的值是 T1事務的值。但根據 pointB 點來看 GSI的讀規則 1.4 的要求,會發現, 如果T3讀到T1的事務的修改,那么必然要求, T3和T1之間沒有空洞。但實際上 T2 是落在了 T3和T1之間的, 也就是說, 違反了 GSI 1.4的讀規則。

  •  所以我們必須規定, SI 讀取時間戳的設置,必須保證比當前系統中正在運行的事務的最小的提交時間戳還要小。

4.MongoDB副本集時間戳應用

MongoDB 4.0的復制也是利用時間戳特性解決了3.x系列MongoDB從節點復制造成從節點性能下降的關鍵方案。

  •  MongoDB oplog 亂序問題
    •  MongoDB主備節點的數據同步并不基于WiredTiger的wal日志來做的。相反,mongodb會將每次操作的數據變更寫入到一個叫做oplog的集合里。
    •  oplog這個集合,雖然名字帶有log,但實際上,它是一個MongoDB的表, 對oplog的寫入,并不是 append的方式修改的, 而是呈現出一種尾部亂序的方式。
    •  對于oplog來說, oplog的讀取順序是按照TS字段來排序的, 跟上層的提交順序無關。所以存在后開始的事務,在oplog先讀取的場景。
  •  oplog 空洞
    •  因為出現了亂序,所以從節點在讀取oplog的時候,就會在某些時間點出現空洞。舉例如下:
    •  時間點1: oplog 順序為: Ta -> Tb, 此時系統中還有一個事務Tc在運行
    •  時間點2: oplog 順序為: Ta -> Tc -> Tb, 當Tc運行結束后, 因為ts的順序, 看起來是將Tc插入到了Ta和Tb之間。
    •  那么當 從節點 在時間點1 reply 到 Tb的時候, 實際上是漏了 Tc的,這個就是oplog的空洞, 他產生的原因是因為,從節點如果每次讀取oplog最新的數據,就有可能會得到一個不連續的數據, 例如 時間點1上 Ta-> Tb. 這就是oplog空洞。
    •  在具體復制邏輯中,我們必須想辦法來從節點讀取到含有空洞的oplog數據。這也是GSI的要求, snapshot的選取不能含有空洞。
    •  因為 oplog的Ts是mongo上層給的,我們很容易知道哪些事務有哪些ts, 我們再將這個ts 作為事務的commitTs 放到 oplog存儲的事務里, 這樣我們讀取 oplog的順序事務的可見性順序相一致了,在這種情況下,我們就可以 根據 活躍事務列表, 就可以將oplog 分為兩個部分,
    •  假設活躍commitTs列表的事務是 {T10, T11, T12}, 活躍事務列表是 {T10, T11, T12, T13, T14}, 那么意味著, 目前有 T10, T11, T12, T13, T14 再運行,并且 T10, T11, T12 已經設置了 commitTs, 又因為 上面討論的 commitTs 是單調遞增的, 那么我們可知, T13, T14 的commitTs 一定大于 maxCommitTs(T10, T11, T12), 而且我們還可知, minCommitTs(T10,T11,T12) 就是全局最小的 commitTs, 而小于這些的 commitTs的事務,因為不在 活躍事務列表里了, 表示已經提交了, 那么我們可以知道, oplog ts 在 全局最小的 commitTs 之前的, 就是都提交了的, oplog 按照 commitTs 排序后,如下所示

… Tx | minCommitTs(T10,T11,T12) | …

我們可以知道 T9, 或者說小于 minCommitTs(T10,T11,T12) 都是無空洞,因為系統不會再提交小于 minCommitTs(T10,T11,T12) 的事務到oplog里了, 所以從節點可以直接恢復這里的數據。

  •  上面說的oplog minCommitTs(T10,T11,T12) 在 mongodb里,就是特殊的timestamp, 這個后文會講。
  •  通過上面的方案,我們可以解決空洞的問題。這個時候,從節點每次恢復數據的時候,將讀取的snapshot,設置為上一次恢復的Ts(同樣也是無空洞的Ts), 這樣的話, 從節點的恢復數據和讀取數據也就做到了互不沖突。從而解決了 3.x系列的 從節點同步數據造成節點性能下降的問題。 
分享到:

相關信息

系統教程欄目

欄目熱門教程

人氣教程排行

站長推薦

熱門系統下載