天天看點

【MongoDB】WriteConcen寫顧慮

    mongodb有一個write concern的設定,作用是保障write operation的可靠性。一般是在client driver裡設定的,和db.getLastError()方法關系很大。

    一般來說,所有的mongo driver,在執行一個寫操作(insert、update、delete)之後,都會立刻調用db.getLastError()方法。這樣才有機會知道剛才的寫操作是否成功,如果捕獲到錯誤,就可以進行相應的處理。處理邏輯也是完全由client決定的,比如寫入日志、抛出錯誤、等待一段時間再次嘗試寫入等。作為mongodb server并不關心,server隻負責通知client發生了錯誤。

    這裡有2點需要注意:

db.getLastError()方法是由driver負責調用的,是以業務代碼不需要去顯式調用。

driver一定會調用db.getLastError()函數,但是并不一定能捕獲到錯誤。這主要取決于write concern的設定級别。

WriteConcern時序圖:

write concern:0(Unacknowledged)

【MongoDB】WriteConcen寫顧慮

從圖可見,driver調用執行Write之後會立刻調用getLastError(),然後立刻傳回結果Response,然後才實際進行寫操作。是以getLastError()的傳回值一定是null,即使之後的Apply發生了錯誤,driver也不知道。使用這個級别的write concern,driver的寫入調用立刻傳回,是以性能是最好的,但是可靠性是最差的,是以并不推薦使用。在各平台最新版本的driver中,也不再以0作為預設級别。其實還有一個w:-1的級别,是error ignored,基本上和w:0差不多。差別在于,w:-1不會捕獲任何錯誤,而w:0可以捕獲network error

write concern:1(acknowledged)

【MongoDB】WriteConcen寫顧慮

和Unacknowledged的差別是,現在mongod隻有在Apply(實際寫入操作)完成之後,才會傳回getLastError()的響應。是以如果寫入時發生錯誤,driver就能捕獲到,并進行處理。這個級别的write concern具備基本可靠性,也是目前mongodb的預設設定級别

write concern:1 & journal:true(Jounaled)

【MongoDB】WriteConcen寫顧慮

Acknowledged級别的write concern也不是絕對可靠的。因為mongodb的Apply操作,是将資料寫入記憶體,定期通過fsync寫入硬碟。如果在Apply之後,fsync之前mongod挂了,或者甚至server挂了,那持久化實際上是失敗的。但是在w:1的級别下,driver無法捕獲到這種情況下的error(因為response在apply之後就已經傳回到driver)

mongod解決這個問題的辦法是使用Journal機制,寫操作在寫入記憶體之後,還會寫到journal檔案中,這樣如果mongod非正常down掉,重新開機以後就可以根據journal檔案中的内容,來還原寫操作。在64位的mongod下,journal預設是打開的。但是32位的版本,需要用--journal參數來啟動

在driver層面,則是除了設定w:1之外,再設定journal:true或j:true,來捕獲這個情況下的error。

write concern:2(Replica Acknowledged)

【MongoDB】WriteConcen寫顧慮

這個級别隻在replica set的部署模式下生效,隻有secondary從primary完成了複制之後,getLastError()的結果才會傳回。也可以同時設定journal:true或j:true,則還要等journal寫入也成功後才會傳回。但是注意,隻要primary的journal寫入就會傳回,而不需要等待secondary的journal也寫入。類似的也可以設定w:3,表示至少要有3個節點有資料;或者w:majority,表示>1/2的節點有資料。一般小規模的叢集就是3節點部署,是以配置w:2就可以了。

mongodb資料寫入的順序:

寫入到記憶體:mongodb執行寫入的時候,是首先把資料寫入到記憶體當中,如果在資料寫入到日志檔案journal或者重新整理到硬碟之前mongod挂掉了,那麼資料就會丢失。

寫入到journal:如果打開journal,那麼即使斷電也隻會丢失100ms的資料,這對大多數應用來說都可以容忍了。從1.9.2+,mongodb都會預設打開journal功能,以確定資料安全。而且journal的重新整理時間是可以改變的,2-300ms的範圍,使用 --journalCommitInterval 指令。

寫入到硬碟:資料重新整理到oplog和硬碟的時間間隔是60秒,對于複制來說,不用等到oplog重新整理到硬碟,在記憶體中就可以直接複制到Secondary節點。

getLastError指令

getLastError 是Mongodb的一個指令,從名字上看,它看起來好像是取得最後一個error,但其實它是Mongodb的一種用戶端阻塞方式。用這個指令來獲得目前線程上一個寫操作是否成功的資訊。

getlastError有幾個參數:j,w,fsync。在大多數的語言驅動中,這個指令是被包裝成WriteConcern類,比如java。j表示是否需要等待寫入完日志再傳回成功通知,w表示至少寫入到幾個服務執行個體再傳回成功通知,fsync表示是否需要等待重新整理到硬碟再傳回成功通知。

journal無論如何都是建議打開的,設定j:true,隻是說driver調用getLastError()之後是否要等待journal寫入完成再傳回。并不是說不設定j:true就關閉了server端的journal。

什麼時候使用getLastError指令

Mongodb的寫操作預設是沒有任何傳回值的,這減少了寫操作的等待時間,也就是說,不管有沒有寫入到磁盤或者有沒有遇到錯誤,它都不會報錯。但一般我們是不放心這麼做的,這時候就調用getlastError指令,得到傳回值。

以java為例,舉個例子:當我們為字段建立了一個唯一索引,針對這個字段我們插入兩條相同的資料,不設定WriterConcern或者設定WriterConcern.NORMAL模式,這時候即便抛出異常,也不會得到任何錯誤。insert()函數在java中的傳回值是WriteResult類,這個類實際上包裝了getlastError的傳回值,但是這時候WriteResult的_lastErrorResult屬性實際上是空的。因為dup key錯誤是server error,隻有在WriterConcern.SAFE或更進階别的模式下,才會得到server error。

在多線程模式下讀寫Mongodb的時候,如果這些讀寫操作是有邏輯順序的,那麼這時候也有必要調用getlasterror指令,用以確定上個操作執行完下個操作才能執行,因為兩次執行的連接配接有可能是不同的。在大多數情況下,我們都會使用連接配接池去連接配接mongodb,是以這是需要注意的。

在java等語言中,是不需要顯示調用這個指令的,隻需要設定WriterConcern即可。

getLastError最佳實踐

如果沒有特殊要求,最低級别也要使用WriterConcern.SAFE,即w=1。

對于不重要的資料,比如log日志,可以使用WriterConcern.NONE或者WriterConcern.NORMAL,即w=-1或者w=0,省去等待網絡的時間。

對大量的不連續的資料寫入,如果每次寫入都調用getLastError會降低性能,因為等待網絡的時間太長,這種情況下,可以每過N次調用一下getLastError。但是在Shard架構上,這種方式不一定確定之前的寫入是成功的。

對連續的批量寫入(batchs of write),要在批量寫入結束的時候調用getlastError,這不僅能確定最後一次寫入正确,而且也能確定所有的寫入都能到達伺服器。如果連續寫入上萬條記錄而不調用getlastError,那麼不能確定在同一個TCP socket裡所有的寫入都成功。這在并發的情況下可能就會有問題。避免這個并發問題,可以參考如何在一個連結(請求)裡完成批量操作 http://mongodb.github.io/mongo-java-driver/3.0/driver/reference/crud/

對資料安全要求非常高的的配置:j=true,w="majority" db.runCommand({getlasterror:1,j:true,w:'majority',wtimeout:10000})

java語言可以在MongoOption中設定,MongoOption中的這些設定是全局的,對于單獨的一個(連接配接)操作,還可以分别設定。

總結:

spring-data-mongo中提供的預定義WriteConcern級别如下:

NONE : 沒有異常抛出

NORMAL : 僅抛出網絡異常, 沒有伺服器異常

SAFE : 抛出網絡異常 和 伺服器異常, 并等待伺服器完成寫操作

MAJORITY : 抛出網絡異常 和 伺服器異常, 并等待主伺服器完成寫操作

FSYNC_SAFE : 抛出網絡異常 和 伺服器異常, 寫操作等待伺服器将資料重新整理到磁盤

JOURNAL_SAFE : 抛出網絡異常 和 伺服器異常, 寫操作等待伺服器送出到磁盤的日志檔案

REPLICAS_SAFE : 抛出網絡異常 和 伺服器異常, 并等待至少2台伺服器完成寫操作

spring-data-mongo預定義WriteConcern的構造如下,想檢視詳細情況請閱讀源碼