MongoDB學習6:MongoDB的事務處理


1.MongoDB的寫操作事務

寫入策略 writeConcern

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

什么是writeConcern?

writeConcern決定一個寫操作落到多少個節點上才算成功,這決定了MongoDB是否成功寫入數據。writeConcern的取值有以下:

  • 0:發起寫入操作,不關心是否成功(適用於性能要求高,但不關注正確性的場景)
  • 1-集群最大節點數:寫操作需要被復制到指定節點數才算成功
  • majority:寫操作需要被復制到大多數節點上才算成功(適用於對數據安全性要求比較高的場景,該選項會降低寫入性能
  • all:復制到全部節點上才算成功

發起寫操作的程序將阻塞到寫操作到達指定的節點數為止

writeConcern的行為

以3節點復制集為例:不做任何特定設置


上圖表示一個寫操作進入后,直接寫入主節點成功就返回了,后台會異步復制到從節點secondary1和secondary2。但假如數據剛寫入主節點后,從節點還沒有復制數據,主節點就宕機了,此時MongoDB就可能出現丟失數據的問題,那么如何解決呢?

參數 w:"majority"


majority表示數據寫入大多數(超半數)節點后才算成功。此時如果主節點再發生宕機情況,那么從節點secondary1就會被選舉為新的主節點,數據也沒有丟失

參數 w:"all"


all表示確認全部節點寫入成功后才返回。這是一種最安全的寫法,數據絕對不會丟失,但是如果有一個節點故障,那么就會發生阻塞一直等待

參數 j:true

j表示寫入操作的journal持久化后才向客戶端確認,取值有:

  • true:寫操作落到 journal 文件中才算成功
  • false:寫操作到達內存即算成功

參數 wtimeout: 寫入超時時間,僅w的值大於1時有效

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

writeConcern測試

以下測試在3個節點環境中

db.test.insert({count:1},{writeConcern:{w:"majority"}})
db.test.insert({count:1},{writeConcern:{w:3}})
db.test.insert({count:1},{writeConcern:{w:4}})   # 報錯:Not enough data-bearing nodes

db.test.insert({count:1},{writeConcern:{w:3,wtimeout:3000}})  # 超過3s未響應則不再等待直接返回

slaveDelay:設置節點延遲時間(單位:s),延遲多久才會同步數據

var conf = rs.conf
conf.members[2].salveDelay = 10  #設置節點3延遲10秒
conf.members[2].priority=0  #設置了延遲的節點不能參與選舉

注意事項

  • 雖然多半數的 writeConcern都是安全的,但通常只會設置 majority,因為這是等待寫入延遲時間最短的選擇
  • 不要設置 writeConcern 等於總節點數,因為一旦有一個節點故障,所有寫操作都會失敗
  • writeConcern 雖然會增加寫操作的延遲時間,但並不會顯著增加集群的壓力,因此無論是否等待,寫操作最終都會復制到所有節點上。但設置 writeConcern只是讓寫操作等待復制后在返回而已
  • 應對重要數據(訂單、金融有關的)應用 {w:"majority"},普通數據(日志)可以應用{w:1}以確保最佳性能

2.MongoDB的讀操作事務

在讀取數據的過程中我們需要關注以下問題:

  • 從哪里讀?關注數據節點的位置(由readPerference解決)
  • 什么樣的數據可以讀?關注數據的隔離性(由readConcern解決)

readPerference:

使用:db.collection.find({}).readPerf("secondary")

readPerference決定使用哪一個節點來滿足正在發起的讀請求,可選值包括:
  • primary(默認):只選擇主節點,保證每次讀到的數據都是最新的
  • primaryPerferred:優先選擇主節點,如果不可用則選擇從節點
  • secondary:只選擇從節點
  • secondaryPerferred:優先選擇從節點,如果從節點不可用則選擇主節點
  • nearest:選擇最近的節點,針對多區域部署的情況
readPerference使用場景舉例:
  • 用戶下單后馬上跳轉到訂單詳情頁--primary/primaryPerferred,因為此時從節點可能還沒復制到新的訂單數據
  • 用戶查詢自己下過的訂單--secondary/secondaryPerferred,查詢歷史訂單對時效性通常沒有太高要求
  • 生成報表--secondary,報表對時效性要求不高,但資源需求大,可以在從節點單獨處理,避免影響主節點操作
  • 將用戶上傳的圖片分發到全世界,讓各地用戶就近讀取--nearest,每個地區的應用選擇最近的節點讀取數據
readPerference與Tag

readPreference只能控制使用一類節點。Tag則可以將節點選擇控制到一個或幾個具體的節點,有以下場景:

  • 一個5個節點的復制集
  • 3個節點硬件較好,專用於服務線上用戶
  • 2個節點硬件較差,專用於生成包報表

可以使用Tag來達到這樣的控制目的:

  • 為3個較好的節點打上 {purpose:"online"}
  • 為2個較差的節點打上 {purpose:"analyse"}
  • 在線應用讀取時指定online,報表讀取時指定analyse

readPerference配置:
  • 通過MongoDB的連接字符串:mongodb://host:27017,host2:27017,host3:27017/?replicaSet=rs&readPerferende=secondary
  • 通過MongoDB驅動程序API:MongoCollection.withReadPerference(ReadPerference readPerf)
  • Mongo Shell:db.collection.find({}).readPerf("secondary")
readPerference注意事項:
  • 指定readPerference時也應該注意高可用問題。例如將 readPerference 指定primary,則發生故障轉移不存在primary期間將沒有節點可讀。如果業務允許,則應選擇primaryPerference
  • 使用Tag時也會遇到同樣的問題,如果只有一個節點擁有一個特定的Tag,則這個節點宕機時將無節點可讀。這在有時候是期望的結果,有時候不是:
    • 如果報表使用的節點失效,即使不生成報表,通常也不希望將報表負載轉移到其他節點上,此時只有一個節點有報表Tag是合理的選擇
    • 如果線上節點失效,通常希望有替代節點,則應該保持多個節點有同樣的Tag
  • Tag有時需要與優先級、選舉權綜合考慮。例如做報表的節點通常不會希望它成為主節點,則優先級應為0

readConcern:

使用:db.test.find().readConcern("majority")

在readPerference選擇了指定節點后,readConcern決定這個節點上的數據哪些是可讀的,類似於關系型數據庫的隔離級別,包括:
  • available:讀取所有可用的數據
  • local(默認):讀取所有可用且屬於當前分片的數據
  • majority:讀取再大多數節點上提交完成的數據
  • linearizable:可線性化讀取文檔
  • snapshot:讀取最近快照中的數據
readConcern:local和available

在復制集中local和available是沒有區別的,兩者的區別主要體現在分片集上,有以下場景:

  • 一個chunk x 正在從shard1向shard2遷移
  • 整個遷移過程中chunk x 中的部分數據會在shard1和shard2中同時存在,但源分片shard1仍然是chunk x的負責方
    • 所有對chunk x的讀操作仍然進入shard1
    • config中記錄的信息chunk x仍然屬於shard1
  • 此時如果度shard2,則會體現出local和available的區別:
    • local:只取應該由shard2負責的數據(不包括x)
    • available:shard2上有什么就讀什么(包括x)

readConcern:majority

只讀取大多數數據節點上都提交了的數據,考慮如下場景:

  • 集合中原有文檔 {x:0}
  • 將x值更新為1

    如果在各節點上應用 {readConcern:"majority"}來讀取數據:
readConcern:majority與臟讀

MongoDB中的回滾:

  • 寫操作到達大多數節點之前都是不安全的,一旦主節點崩潰,而從節點還沒復制到該次操作,剛才的寫操作就丟失了
  • 把一次寫操作視為一個事務,從事務的角度來看,可以認為事務被回滾了。
    所以從分布式系統的角度來看,事務的提交被提升到了分布式集群的多個節點級別的“提交”,而不是單個節點的“提交”

在可能發生回滾的前提下考慮臟讀問題:

  • 如果在一次寫操作到達大多數節點前讀取了這個寫操作,然后因為系統故障該操作回滾了,則發生臟讀問題,使用 {readConcern:"majority"}可以有效避免臟讀
readConcern:實現安全的讀寫分離

有以下場景

  • 向主節點寫入一條數據
  • 立即從從節點讀取這條數據

如何保證自己能夠讀取到剛剛寫入的數據?
下述方式有可能讀不到剛寫入的訂單:

db.order.insert({id:100,sku:"kite",q:1})
db.order.find({id:100}).readPerf("secondary")

使用writeConcern + readConcern majority來解決:

db.order.insert({id:100,sku:"kite",q:1},{writeConcern:{w:"majority"}})
db.order.find({id:100}).readPerf("secondary").readConcern("majority")
readConcern:linearizable

只讀取大多數節點確認過的數據。和majority最大差別是保證絕對的操作線性順序-在寫操作自然時間后面發生的讀,一定可以讀到之前的寫

  • 只對讀取單個文檔時有效
  • 可能導致非常慢的讀,因此總是建議配合使用 maxTimeMS
readConcern:snapshot

只在多文檔事務中生效。將一個事務的 readConcern設置為snapshot將保證在事務中的讀:

  • 不出現臟讀
  • 不出現不可重復讀
  • 不出現幻讀

因為所有的讀都將使用同一個快照,直到事務提交為止該快照才被釋放

3.MongoDB的多文檔事務

對事務的使用原則應該是:能不用盡量不用
通過合理的設計文檔模型,可以避免大部分使用事務的必要性,因為事務=鎖,節點協調需要額外開銷,影響性能

MongoDB ACID多文檔事務支持

事務屬性 支持程度
Atomocity 原子性 單表單文檔:1.x就支持
復制集多表多行:4.0復制集
分片集群多表多行:4.2
Consistency 一致性 writeCOncern、readConcern(3.2)
Isolation 隔離性 readConcern(3.2)
Durability 持久性 Journal and Replication

使用方法

MongoDB多文檔事務使用方式和關系型數據庫非常相似,以java為例

try(ClientSession clientSession = client.startSession()){
  clientSession.startTransaction();
  collection.insertOne(clientSession,docOne);
  collection.insertOne(clientSession,docTwo);
  clientSession.commitTransaction();
}

事務的隔離級別

  • 事務完成前,事務外的操作對該事務所做的修改不可訪問
  • 如果事務內使用{readConcern:"snapshot"},則可以達到可重復度 Repeatable Read

事務寫機制

MongoDB的事務錯誤處理機制不同於關系型數據庫

  • 當一個事務開始后,如果事務要修改的文檔在事務外部被修改過,則事務修改這個文檔時會出發Abort錯誤,因為此時修改沖突了,這種情況下,只需要簡單的重做事務就可以了
  • 如果一個事務已經開始修改一個文檔,在事務以外嘗試修改同一個文檔,則事務以外的修改會等待事務完成才能繼續進行

注意事項

  • 可以實現和關系型數據庫類似的事務場景
  • 必須使用與MongoDB4.2兼容的驅動
  • 事務默認必須在60s(可調)內完成,否則將被取消
  • 涉及事務的分片不能使用仲裁節點
  • 事務會影響chunk遷移效率。正在遷移的chunk也可能造成事務提交失敗(重試即可)
  • 多文檔事務中的讀操作必須使用主節點讀
  • readConcern只應該在事務級別設置,不能設置在每次讀寫操作上


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM