MongoDB丟資料問題的分析

2021-09-08 18:42:54 字數 3021 閱讀 5814

坊間有很多傳說mongodb會丟資料。特別是最近有乙個infoq翻譯的sven的一篇水文(為什麼叫做水文?因為裡面並沒有他自己的原創,只是蒐羅了一些網上的部落格,炒了些冷飯吃),其中又提到了丟資料的事情。大家知道作為乙個資料庫來說,資料的永續性基本上是資料庫的最低要求了。如果mongodb真的有那麼糟糕的資料安全問題,它早就在技術選擇眾多的今天被無情地淘汰掉了。那麼真相到底如何呢?

實事求是地來說,mongodb確實在其發展的過程中,有一些資料持久化的問題沒有處理好,特別是一些預設值的選定上。大部分使用者會拿來就用,直到遇到問題之後才發現他們應該在開始的時候做一些必要的配置。但是,所有這些已經被發現的問題也好,預設設定也好,已經在 mongodb 2.6以後得到了妥善的解決。我可以負責任地告訴你,你看到的資料安全問題,基本上都是2.4或者之前版本的問題或者是使用者配置的問題。接下來我們來仔細分析一下mongodb的資料安全機制,通過這個分析來更好地理解為什麼有丟資料問題的說法,以及如何來正確的配置mongodb來保證資料的安全。

mongodb的資料安全包括以下幾個概念:

在mysql, postgresql,oracle等關係型資料庫裡都有乙個write ahead log(redo log)的機制用來解決因為系統掉電或者崩潰時導致記憶體資料丟失問題。mongodb 的journal就是實現這個目的的一種wal日誌。在mongodb 2.0之前,journal沒有被支援或者不是乙個預設開的選項。所以當你進行寫入操作時。在沒有journal的情況下,mongodb是這樣儲存資料的:

簡單來說,資料在寫入記憶體之後即刻返回給應用程式。而資料刷盤動作則在後台由作業系統來進行。mongodb會每隔60秒強制把資料刷到磁碟上。那麼大家可以想象得到,如果這個時候發生了系統崩潰或者掉電,那麼未刷盤的資料就會徹底丟失了。如果大家看到的部落格是2023年左右的,那基本上是碰到了這種情況。

自從2.0開始,mongodb已經把journal日誌設為預設開啟。

在上圖的情況下,mongodb會先把資料更新寫入到journal buffer裡面然後再更新記憶體資料,然後再返回給應用端。journal會以100ms的間隔批量刷到盤上。這樣的情況下,即使出現斷電資料尚未儲存到檔案,由於有journal檔案的存在,mongodb會自動根據journal裡面的操作歷史記錄來對資料檔案重新進行追加。

有細心的同學可能注意到,journal檔案是100ms 刷盤一次。那麼要是系統掉電正好發生在上一次刷journal的50ms之後呢?這個時候,我們就可以來看一下mongodb持久化的下乙個概念了:寫關注

寫關注(或翻譯為寫安全機制)是mongodb特有的乙個功能。它可以讓你靈活地指定你寫操作的持久化設定。這是乙個在效能和可靠性之間的乙個權衡。 寫關注有以下幾個級別:

unacknowledged指的是對每乙個寫入操作,mongodb並不會返回乙個是否成功的狀態值。這個級別是寫入效能最好但也是最不安全的級別。比如說,你試圖插入乙個違反了唯一性的文件(重複的身份證號),那麼mongodb會拒絕寫入並報錯。但是由於驅動端並沒有在乎你的報錯,應用程式還滿心歡喜以為一切都沒問題,下回再來查詢那條資料的時候就會出現資料缺失的情況。

在mongodb 2.4,這個設定已經被改為下面的

acknowledged 的意思就是對每乙個寫入mongodb都會確認操作的完成狀態,不管是成功還是失敗。當然這個確認只是基於主節點的記憶體寫入。但就是這個級別,可以偵測到重複主鍵, 網路錯誤,系統故障或者是無效資料等錯誤。

自2.4版本起,mongodb的預設寫安全設定就是 acknowledged。在這種情況下,出現因為系統故障掉電原因而導致的資料丟失只會是我們早些提到的日誌沒有及時刷盤的情況。如果你不能接受因為系統崩潰而引起的可能的100ms的資料損失,那麼你可以選用下乙個級別: journaled

使用這種方式意味著每一次的寫操作會在mongodb實實在在的把journal落盤以後才會返回。當然這並不意味著每乙個寫操作就等於乙個io。mongodb並不會對每乙個操作都立即刷盤,而是會等最多30ms,把30ms內的寫操作集中到一起,採用順序追加的方式寫入到盤裡。在這30ms內客戶端執行緒會處於等待狀態。這樣對於單個操作的總體響應時間將有所延長,但對於高併發的場景,綜合下來平均吞吐能力和響應時間不會有太大的影響。特別是你能給journal部署乙個對順序寫有優化的io頻寬足夠的專門的儲存系統的話,這個對效能的影響可以降到最低。

那麼使用 是不是就100% 安全了呢?如果是單機版本,這個基本上就是可以確保的了(除非硬碟損壞)。可是在複製集的場景下,我們還需要來考慮一種更高的級別:

mongodb 的預設部署是至少3個節點的複製集(replicaset)。使用複製集的好處很多,最關鍵的就是提高系統的高可用性。另外乙個好處就是提供資料的永續性。在複製集下哪怕你的整個主機連記憶體帶硬碟壞掉,你的資料還是健康的存在在第二台或者第n臺從節點上。但是複製集作為一種分布式的架構也對我們資料一致性提出了新的挑戰。以上述的 寫安全配置為例,我們來分析一種比較複雜的場景。

在這個時候應用如果再去查詢 這個文件,mongodb 將會說文件不存在!

怎麼辦怎麼辦? 就是我們的答案。 「majority」 指的是「大多數節點」。使用這個寫安全級別,mongodb只有在資料已經被複製到多數個節點的情況下才會向客戶端返回確認。

我們來看一下在使用 之後,剛才的情況就變成了:

至此,如果使用 , 那麼mongodb可以滿足所有級別資料永續性的要求。值得注意的是在2023年5月kyle kingsly 發表了一篇部落格 call me maybe:  在這片文章裡kyle 匯報了一些關於 的bug, 這些bug已經在2.6裡被解決了。當然像sven那樣的譁眾取寵之流,估計並沒有去研究3.0裡面是否真的有問題,而是隨便google了一下幾年前的東西來做文章。

一般來說,mongodb建議在集群中使用 設定。在乙個集群是健壯的部署的情況下(如:足夠網路頻寬,機器沒有滿負荷),這個可以滿足絕大部分資料安全的要求,因為mongodb的複製在正常情況下是毫秒級別的,往往在journal刷盤之前已經複製到從節點了。如果你追求完美,那麼可以再進一步使用 。兩者相結合,

傳說中mongodb 丟資料的事情,確實已經成為傳說了。

後記:在我寫這篇文章之時,社群又有人匯報資料丟失的問題。說的是每一萬條記錄就會丟一兩條記錄。對於這種情況,我的第一反應就是:查查你的**吧,很多時候往往問題出在程式上。果不其然,經過仔細檢查,原來是**的問題。

kafka丟資料 重複資料分析

填別人的坑again。資料不正常,追到kafka這裡了,分析了很久的程式,做乙個總結,關於丟資料和重複資料。先說丟資料。目前遇到一種丟資料的情況。如果auto.commit.enable true,當consumer fetch了一些資料但還沒有完全處理掉的時候,剛好到commit interval...

linux 系統 UDP 丟包問題分析思路

序言 在開始之前,我們先用一張 釋 linux 系統接收網路報文的過程。首先網路報文通過物理網線傳送到網絡卡 網路驅動程式會把網路中的報文讀出來放到 ring buffer 中,這個過程使用 dma direct memory access 不需要 cpu 參與 核心從 ring buffer 中讀...

Kafka丟資料的情況

1.消費端弄丟了資料 消費者自動提交offset會丟資料,只要關閉自動提交 offset,在處理完之後自己手動提交 offset,就可以保證資料不會丟。2.kafka 弄丟了資料 kafka宕機重新選舉leader的時候,要是 follower沒有及時同步資料,就會丟資料。在 kafka 服務端設定...