mongodb-事務(wù)
前言
寫事務(wù)
使用 writeConcern 保證數(shù)據(jù)準確落盤
讀事務(wù)
readPreference 來確定從哪里讀
readConcern 來確定可以讀什么樣的數(shù)據(jù)
多文檔事務(wù)
前言
事務(wù)是 mongoDB 中非常核心的一個功能,在 4.0 版本以前,mongoDB 只支持單個文檔的事務(wù),在 4.0 和 4.2 版本之后,分別支持了復(fù)制集事務(wù)和分片事務(wù),也可以說在大多數(shù)的數(shù)據(jù)庫中都是非常重要的一個功能,值得我們單獨拉一章去講解
那「怎么樣在 mongoDB 中合理的使用事務(wù)來保證數(shù)據(jù)安全呢」?
后續(xù)我將會從讀、寫和多文檔事務(wù)這三個方向去闡述
寫事務(wù)
使用 writeConcern 保證數(shù)據(jù)準確落盤
writeConcern 中有兩個選項
w(決定一條數(shù)據(jù)落到寫到多少個節(jié)點才算真正成功) 0:不關(guān)心(最不安全) 數(shù)字:寫到 n 個節(jié)點才算成功(自定義) majority:寫入至少一半的節(jié)點才算成功(推薦,性能和安全均衡) all:全部寫完才算成功(性能差點,很安全,但是只要有一個失敗就會失敗) j(決定怎樣才算真正成功) true:寫入 journal 日志 才算成功 false:寫入內(nèi)存就算成功
??db.collection.insert({a:1},{writeConcen:{w:"majority",j:true});
對于一些「普通數(shù)據(jù)可以使用 w:1 來確保最佳性能」,對于「重要數(shù)據(jù)可以用 w:majority 來保證數(shù)據(jù)安全」
讀事務(wù)
readPreference 來確定從哪里讀
readPreference 有幾個屬性
primary:只從主節(jié)點讀 primaryPreferred:先讀主節(jié)點,如果掛了再讀從節(jié)點 secondary:只從從節(jié)點讀 secondaryPreferred:先讀從節(jié)點,如果掛了在讀主節(jié)點 nearest:讀最近的節(jié)點
「primiry 和 ?primaryPreferred ?適用于對延遲敏感讀較高」的數(shù)據(jù),比如訂單信息
「secondary 和 secondaryPreferred 適用于對延遲敏感度要求較低」的數(shù)據(jù),比如日志信息
「nearest 適用于業(yè)務(wù)域較廣的應(yīng)用」,比如將業(yè)務(wù)信息同步到全球各地的節(jié)點,「中國用戶會訪問中國的節(jié)點,俄羅斯用戶會訪問俄羅斯的節(jié)點」, nearest 的判斷也是比較簡單的,直接是使用應(yīng)用到 mongo 服務(wù)器的的 ping time 來決定
當然,還有一種是給「服務(wù)器打標簽(tag) 的方式」,比如要將讀取操作定向到標記有 ?"name": "a"和"key": "person"的輔助節(jié)點集:
db.collection.find({}).readPref(?"secondary",?[?{?"name":?"a",?"key":?"person"?}?]?)
readConcern 來確定可以讀什么樣的數(shù)據(jù)
readConcern 有幾個屬性
available:讀取所有可用的數(shù)據(jù) loacl:讀取所有可用且僅屬于當前分片的數(shù)據(jù) majority:讀取大多數(shù)節(jié)點都寫入的數(shù)據(jù) 「通過快照來維護多個不同的版本,使用 MVCC 實現(xiàn)」,每個被大多數(shù)節(jié)點確認過的數(shù)據(jù)就是一個快照 linearizable:可線性化讀取文檔 有時會被阻塞,其保證如果一個線程已經(jīng)完成了寫入并且告知了其他線程,那么這其他的線程就可以看到這些改動。如果某一瞬間你的副本集出現(xiàn)了兩個主節(jié)點(有一個還未來得及降級)然后你從這個老的主節(jié)點上進行讀取,與此同時新的主節(jié)點上已經(jīng)有了新的數(shù)據(jù),你讀到的數(shù)據(jù)就是舊數(shù)據(jù) snapshot:讀取快照中的數(shù)據(jù)(類似于可串行化)

loacl 和 available 的區(qū)別體現(xiàn)在分片集群中的 chunk 遷移上,如果讀 shard2 ,loacl 不能讀到 x ,但是 available 可以讀到
多文檔事務(wù)
4.0 版本 mongoDB 支持了復(fù)制集的多文檔事務(wù)
4.2 版本 mongoDB 支持了分片集群的多文檔事務(wù)
也就是說是說,mongoDB 在 4.2 版本的是有擁有了和 mysql 這種關(guān)系型數(shù)據(jù)庫一樣的事務(wù)能力,這對于業(yè)務(wù)的選擇角度來講,又給 mongoDB 添加了一筆濃重的色彩
在整個數(shù)據(jù)庫的分布式事務(wù)當中,還需要重點提一嘴的就是時間問題,我們先來看看會有什么問題存在

比如有兩個操作發(fā)向 a、b 兩個節(jié)點
客戶端將 a = 1 發(fā)向 a、b 節(jié)點 a 節(jié)點操作 a =1 客戶端將 a = a +1 發(fā)送給 a、b 節(jié)點 a 節(jié)點操作 a = a + 1 b 節(jié)點由于業(yè)務(wù)網(wǎng)絡(luò)等原因先執(zhí)行了 a = a + 1,后操作了 a = 1
最后我們就發(fā)現(xiàn),a、b 兩個節(jié)點的數(shù)據(jù)不一致了,那么 mongo 是怎么解決的呢,一般是兩種方式:
「全局授時」;比如我們可以采用GPS時鐘或者是NTP服務(wù)這種全局授時點
「邏輯時鐘」:也就是我們采用一種局部的時間戳的方式去演進,這個就叫邏輯時鐘。
mongo 采用的是「混合邏輯時鐘」:
在這個混合邏輯時鐘中,將物理時鐘和邏輯時鐘混合起來做一個全局的時間出來處理。我們的混合邏輯時鐘會采用一種本地的推進方式,這個就是剛才說的一個接受的時候,他會比較本地的時間戳,然后在本地時間戳、本地真實的物理時間和收到最短 request 的時間,「三者取最大的時間,作為本地時間的一個推進」,需要說明的是,這個時間戳的分配是取決于 oplog 的時間戳。只有「當 oplog 真正寫入數(shù)據(jù)的時候,本地的邏輯時鐘才會向前推進」。在整個混合邏輯時鐘,在整個集群中采用動態(tài)推進的方式,「每一條發(fā)送和接收的請求,都會依據(jù)請求中的時間來推進本地的時鐘」,這樣在全局的情況下,每個節(jié)點的混合邏輯時鐘最終會趨同,趨向同一個地址,趨向同一個時間。這樣的話,剛才說的時間偏差就已經(jīng)不存在了,才可以在集群中做分布式事務(wù)。
再說說 mongo 提交事務(wù)的過程吧
mongoDb 的分布式事務(wù)和 mysql 一樣,也是基于「兩階段協(xié)議」。
第一階段就是 prepare 階段,在 prepare 過程中,所有的 coordinator 會向所有的節(jié)點去發(fā)送 prepare 命令,所有的節(jié)點收到了這個命令以后會返回自己的 prepare timestamp,然后由協(xié)調(diào)節(jié)點去決定選取一個最大的 prepare ts 作為 commit timestamp。
coordinator 和所有的 shard 之間的通訊會促使所有的事務(wù)參與者得到一個協(xié)調(diào)一致的 HLC。在這種邏輯時鐘一致的情況下,commit timestamp 就是全局順序一致的。
第二階段的話就是提交階段, coordinator 會將剛剛的 committed ts 作為 commit timestamp 的時間戳,然后向所有的節(jié)點去廣播。
需要關(guān)注一點,就是在對具有 prepare timestamp 的事務(wù)進行讀取的時候,如果當前的事務(wù)是處于 prepare 狀態(tài)的,并不確定自身的讀時間戳和 prepare 狀態(tài)的大小的話,需要去一直等待這個事務(wù),等到事務(wù)提交或者 abort 以后才去會處理,這個就是剛才所說的。
https://mongoing.com/archives/77608
巨人的肩膀
mongoDB 整個事務(wù)實現(xiàn)的方式都是按照「讀提交」這種關(guān)系來設(shè)計的,也就是說,在客戶端讀取數(shù)據(jù)的時候,只能讀到該事務(wù)節(jié)點前已經(jīng)做了 commit 的數(shù)據(jù)。
