多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > 互聯網 > 五個解決方案讓MongoDB擁有RDBMS的魯棒性事務

五個解決方案讓MongoDB擁有RDBMS的魯棒性事務

來源:程序員人生   發布時間:2014-09-15 04:25:55 閱讀次數:3246次

【編者按】在分布式存儲解決方案中談事務一直是件很痛苦的事情,而事務也成了大部分NoSQL解決方案短板所在。近日,MongoDB公司的Antoine Girbal在其個人博客上撰文,分享了在MongoDB文檔間實施魯棒可擴展事務的5個解決方案――同步字段、作業隊列、二階段提交、Log Reconciliation和版本控制。


免費訂閱“CSDN大數據”微信公眾號,實時了解最新的大數據進展!

CSDN大數據,專注大數據資訊、技術和經驗的分享和討論,提供Hadoop、Spark、Imapala、Storm、HBase、MongoDB、Solr、機器學習、智能算法等相關大數據觀點,大數據技術,大數據平臺,大數據實踐,大數據產業資訊等服務。


以下為譯文:

事務問題

數據庫支持數據塊間的事務是有原因的。典型的場景是應用需要修改幾個獨立的比特時,如果只有一些而不是全部改變存儲到了數據庫,那么這就會出現不一致問題。因此ACID的概念是:

  • 原子性:所有的改變要么都做了,要么都沒做
  • 一致性:數據保持一致性狀態
  • 隔離性:其它用戶看不到部分改變
  • 持久性:一旦向用戶確認了事務,數據就處于安全的狀態(通常存在硬盤上)

引入NoSQL數據庫后,文檔間ACID事務的支持通常就取消了。許多鍵/值存儲仍有ACID,但它只適用于單個條目,取消ACID的主要原因是其可擴展限制。如果文檔橫跨幾個服務器,事務將會很難實施以及性能。假設事務橫跨數十個服務器,一些數據庫是遠程的,一些是不可靠的,想象下這會變的多難,多慢!

在單個文檔等級上,MongoDB支持ACID。更準確的說,默認情況下是“ACI”,打開“j”WriteConcern選項后是ACID。Mongo有豐富的查詢語言,橫跨多個文檔,因此人們一直在尋找多文檔事務來使用他們的SQL代碼。一個常見的辦法是利用文檔的性質:不需要很多行、很多關系,你可以將所有的東西嵌入到一個大文檔中,Denormalization將帶你回歸事務。

這個技術解決了從一對一關系到一對多關系的很多事務問題。這也可能使應用更簡單,數據庫更快,所以這是雙贏。不過當數據庫必須分離時,該怎么辦?

減少ACID

其實大部分應用都可以歸結為:

  • 原子性:實際上你希望所有的改變都完成
  • 一致性:系統短時間不一致沒關系,只要最終一致就行
  • 隔離性:缺乏隔離性導致暫時的不一致,這并不理想,但是當今線上服務時代,很多用戶對此都習慣了(如用戶支持:“它要花幾秒傳輸”)。
  • 持久性:很重要,要支持。

這樣問題就簡化為魯棒性、可擴性、最終一致性。

解決方案 1:字段同步

這種解決方案的使用場景最簡單,最常見:文檔間有些字段需要保持“同步”。例如,你有一個用戶名為“John”的用戶文檔,文檔代表John發表過的評論。如果用戶可以更換用戶名,那么這個改變需要發送給所有文檔,即使進程中有應用錯誤或數據庫錯誤。

為了實現這一目標,一個簡單的辦法是在主文檔(這個情況下主文檔是用戶文檔)中使用一個新字段(如“syncing”)。給“syncing”設置一個日期時間戳,記錄用戶文檔的更新。

db.user.update({ _id: userId }, { $set:{ syncing: currentTime }, { rest of updates ... } })

然后應用會修改所有的評論文檔。結束后,需要移除標識:

db.user.update({ _id: userId }, {$unset: { syncing: 1 } })

現在假設進程中出現了問題:有些評論使用的是舊用戶名。不過這些地方仍然會保留標識,所以應用知道哪些進程需要重新進行。因此,你需要后臺進程在指定的時間(如1小時)檢查“syncing”文件是否有未完成的地方。索引應設為“sparse”,這樣只有實際設置的文檔需要被索引,索引量就會比較小。

db.user.ensureIndex({ syncing: 1 }, { sparse: true })

因此,系統通常可以保持事情在短時間內同步,在系統故障的情況下,時間周期為一個小時。如果時間不重要,當探測到“syncing”標志時,應用可以輕易修復文檔。

解決方案2:作業隊列

以上原理良好工作的前提是應用不需要很多內容,只依賴于通用進程(如:復制一個值)。一些事務需要執行特定變化,這些變化稍后很難識別。例如,用戶文檔包括一個朋友列表:

{ _id: userId, friends: [ userId1,userId2, ... ]}

現在A和B決定成為朋友:你需要把B添加到A的列表,也需要把A添加到B的列表。如果兩者沒有同時發生也沒有關系(只要沒有引發困擾)。針對這種情況和大多數事務問題的解決方案是使用作業隊列,作業隊列也存儲在MongoDB。一個作業文檔就像這樣:

{ _id: jobId, ts: timeStamp, state: "TODO", type: "ADD_FRIEND", details: { users: [ userA, userB ]} }

或者是原始線程可以插入作業轉發改變,或者是“worker”線程可以撿起工作。worker使用findAndModify()獲取最原始的未加工的工作,findAndModify()是完全原子性的。操作中findAndModify()將工作標注為將被處理,同時也會表明worker name、當前時間以便于追蹤。{ state: 1, ts: 1 } 上的索引使這些調用很迅速。

db.job.findAndModify({ query: { state: "TODO" }, sort: { ts: 1 }, update: { $set: { state: "PROCESSING", worker: { name: "worker1", ts: startTime } } } })

之后worker以一種冪等的方式對雙方用戶文檔進行修改,這些改變能應用很多次,并且有同樣的效果――這很重要!為了這個目的,我們只需要使用一個$addToSet。一種更通用的替代方式是在查詢端添加一個測試,檢測修改是否執行了。

db.user.update({ _id: userA }, {$addToSet: { friends: userB } })

最后一步是刪除作業或標注作業完成。再保留一段時間作業是一種安全的方式,唯一的缺點是隨著時間的流逝,先前的索引會變得越來越大,盡管你可以在指定域{ undone: 1 } 上使用稀疏索引,并且根據實際情況修改查詢。

db.job.update({ _id: jobId }, { $set: { state: "DONE" } })

如果進程在某一時刻故障了,作業仍然會在隊列中,并標注為處理中。后臺進程停止一段時間后會將作業標注為需要再次處理,然后作業會重新從頭開始。

解決方案3 :二階段提交

二階段提交是一個眾所周知的解決方案,很多分布式系統都采用了這種解決方案。MongoDB簡化了這種解決方案的實施,因為靈活的框架,我們可以將所有需要執行的數據全都放入文檔中。我幾年前就寫過關于這種方法的文章,你可以去MongoDB Cookbook中查閱《 執行二階段提交》(Perform Two Phase Commits)或者到MonoBD Manual中查閱《 執行二階段提交》(Perform Two Phase Commits)。

解決方案4: Log Reconciliation

很多財務系統常用的解決方案是 log reconciliation。這種解決方案將事務寫作簡單的日志,這避免了復雜性和潛在的故障。然后從上次良好狀態以來所有的變化推測當前賬戶的狀態。在極端情況下,你可以清空賬戶,然后通過實施從第一天以來所有的變化重建賬戶……這聽起來很恐怖,但是可行。賬戶文件需要一個“緩存”來提高速度,還需要一個seqId,seqId計算如下:

{ _id: accountId, cache: { balance:10000, seqId: 115 } }

執行事務時,一個典型的財務系統會給事務寫一個條目,會給與事務有關的賬戶寫一個“賬戶變化”條目。這個方法需要進一步的寫保證,“作業隊列”解決方案可以實現寫保證,事務中所有的作業在所有賬戶更改寫入前都會保持不變。不過有了MongoDB,我們可以寫一個包括事務和賬戶更改的文檔。這個文檔應該嵌入tx集合,如下:

{ _id: ObjectId, ts: timestamp , proc: "UNCOMMITTED", state: "VALID", changes: [ { account: 1234, type: "withdraw", value: -100, seqId: 801, cachedBal: null }, { account: 2345, type: "deposit", value: 100, seqId: 203, cachedBal: null } ] }

幾個重點:

  • 步驟:事務從“UNCOMMITTED” 狀態開始,變為“COMMITTED”,此時涉及這些賬戶的所有先前事務也會變為 “COMMITTED” ,這表明這個事務也可以用作“anchor”來進行平衡計算。
  • 狀態:狀態可能是 “VALID”、“CANCELLED等。如果不是VALID,即使是“COMMITTED”,平衡計算也會忽略事務。
  • seqId:這是賬戶的獨有的seqId,這個seqId給賬戶更改一個確定的順序。
  • cachedBal:賬戶的緩存平衡。如果事務時“COMMITTED”狀態,那么緩存平衡(如果設置了)是一個有效值。
  • 注意我們在 { changes.account: 1, changes.seqId: 1 }上使用一個獨特的索引。reconciliation需要這個索引來提速,一個賬戶也不會有seqId副本。

關鍵是確保即使事務沒有按順序發生,緩存平衡也可以安全的計算/取消,還有就是事務狀態可能改變。因此我們每個賬戶使用一個seqId,這確保了賬戶更改按確定的順序發生,可以避免復雜的鎖。在寫事務前,應用首先通過簡單地查詢推斷每個賬戶的下一個sqlId:

db.tx.find({ "changes.account": 1234 }, { "changes.$.seqId": 1 }).sort({ "changes.seqId": -1 }).limit(1)

然后每個sqlId都本地增長,然后寫作事務的一部分。如果另一個線程也可能同時包括同樣的seqId,獨特的索引會確保寫失敗,線程會進行重試直到順利完成任務。另一種方法是在賬戶集中保存一個當前seqId,然后用 findAndModify()獲得下一個seqId,這通常會比較慢,除非你對賬戶有很多爭用。注意如果因為某種原因事務沒有寫時,seqId可能會被跳過去,不過只有沒有副本情況下才會成為。

下面我們談談reconciliation的基礎。后臺進程確保所有未提交的事務都會繼續進行。只有所有賬戶的低seqId的事務都提交后一個事務才會被標注為提交。事務被標記為提交后就會變成不可變的。下面來談談好的方面:獲得賬戶平衡。首先我們獲得好的平衡,我們可以通過索引進行查詢:

db.tx.find({ "changes.account": 1234, proc: "COMMITTED" }, { "changes.$": 1 }).sort({ "changes.seqId": -1 }).limit(1)

我們通過較大seqId的事務獲得所有將發生的更改:

db.tx.find({ "changes.account": 1234, "changes.seqId": { $gt: lastGoodSeqId } }, { "changes.$": 1 }).sort({ "changes.seqId": 1 })

我們可以使用這些解決展示即將發生的損耗。如果我們只想簡單的了解將來的平衡點在哪,我們可以讓MongoDB收集所有變更展示總數:

db.tx.aggregate([{ $match: { "changes.account": 1234, "changes.seqId": { $gt: lastGoodSeqId }, state: "VALID" }}, 
{ $unwind: "changes" }, 
{ $match: { "account": 1234 }}, 
{ $group: { _id: "total", total: { $sum: "$value" } }}])

為了確保系統快速、計算量小,后臺工作者要確保所有的事務都達到提交狀態,平衡得到緩存。理想情況下一個事務是不可逆的,取而代之的是提交一個逆向事務來實施事務。不過只要所有的進一步事務狀態和緩存都是正確設置的,取消是可行的。

解決方案5:版本控制

有時變得很復雜,以至于不能再JSON中表示,這些變更可能涉及很多有著復雜關系的文件(如樹結構)。如果僅是部分變化(如破壞樹)將會很混亂,這種情況下我們需要隔離。獲取隔離性的一種方式是插入有著高版本號的新文檔,取代對現有文檔的更新。可以通過同日志和解同樣的技術很容易、很安全的獲得新版本號。通常{ itemId: 1, version: 1}上有一個獨特的索引。

嵌入文檔的應用從子文檔開始,到主文檔結束(如根節點)。當獲取數據時,應用檢查主文檔的版本號,忽略高于版本號高于此版本號的文檔。未完成的事務可以保持原狀,可以忽略,可以清楚。

總結

綜上所述,我們提供了在文檔間實施魯棒可擴展事物的五種解決方案:

  • 同步標志:最適用于僅從主文檔復制數據的情況
  • 作業隊列:比較通用,適用于95%的情況,大部分系統至少需要一個作業隊列
  • 二階段提交:這種技術確保每個實體都有為保持一致性狀態所需的所有信息
  • Log Reconciliation:最魯棒的技術,最適用于財務系統
  • 版本控制:提供了隔離性,適用于復雜的結構

此外,我們還提到了很多次MongoDB最終將支持真正的原子性和文檔間的隔離事務。這已經作為分區的一部分了,但目前還只是內部的……只有文檔在同一分區時這一特性才可能實現,否則我們將回到不可擴展的SQL世界。

原文鏈接: How to Implement Robust and Scalable Transactions Across Documents with MongoDB(編譯/蔡仁君 責編/仲浩)

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 中文字幕人成乱码中国 | 国产v片| 在线观看中文字幕国产 | 国产男女爽爽爽爽爽免费视频 | 欧美一区二区三区国产精品 | 最新国产在线视频 | 国产成人美女福利在线观看 | 日韩精品久久不卡中文字幕 | 精品视频久久久久 | 欧美精品videossex欧美性 | jizz性欧美3| 亚洲欧美精品一区 | 一本一道久久综合狠狠老 | 国产免费一区2区3区4区 | 爱爱小视频在线观看网站 | wwwww在线观看| 亚洲噜噜噜噜噜影院在线播放 | 中文字幕在线观看免费视频 | 精品久久综合一区二区 | 免费亚洲视频在线观看 | freesex呦交6一12东 | 国内精品一区二区 | 国产婷婷丁香久久综合 | 欧美久久久久久久久 | 国内精品视频免费观看 | 亚洲欧美自拍另类图片色 | 国产成人一区二区三区视频免费蜜 | 叼嘿免费 | 欧美性xxx久久 | 日韩天天摸天天澡天天爽视频 | 午夜秋霞成人理论 | 1v1双性受整夜不拔bl | 国产精品日韩欧美一区二区三区 | 国产中文字幕在线免费观看 | 亚洲成人黄色在线 | 国产精品国产三级国产 | 欧美国产一区二区二区 | jzz欧美| 亚洲精品久久久久久久久久ty | 国产成人毛片毛片久久网 | 成人自拍视频 |