新聞中心
前言
事務(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ù)準(zhǔn)確落盤。
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 來決定。
當(dāng)然,還有一種是給「服務(wù)器打標(biāo)簽(tag) 的方式」,比如要將讀取操作定向到標(biāo)記有 "name": "a"和"key": "person"的輔助節(jié)點集:
db.collection.find({}).readPref( "secondary", [ { "name": "a", "key": "person" } ] )readConcern 來確定可以讀什么樣的數(shù)據(jù)。
readConcern 有幾個屬性。
- available:讀取所有可用的數(shù)據(jù)。
- loacl:讀取所有可用且僅屬于當(dāng)前分片的數(shù)據(jù)。
- majority:讀取大多數(shù)節(jié)點都寫入的數(shù)據(jù)。
「通過快照來維護多個不同的版本,使用 MVCC 實現(xiàn)」,每個被大多數(shù)節(jié)點確認(rèn)過的數(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ù)當(dāng)中,還需要重點提一嘴的就是時間問題,我們先來看看會有什么問題存在。
比如有兩個操作發(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 的時間戳。只有「當(dāng) 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ù)進行讀取的時候,如果當(dāng)前的事務(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ù)。
網(wǎng)站欄目:一起學(xué)mongodb第五卷之事務(wù)
文章地址:http://fisionsoft.com.cn/article/cdcgjpe.html


咨詢
建站咨詢
