MongoDB的學習,寫入安全的解析

這裡引入一位大神的寫的文章,很好解釋了寫入安全策略。

<code>原文鏈接:https://blog.csdn.net/kyfxbl/article/details/12402651

作者:kyfxbl/<code>

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

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

這裡有2點需要注意:

1、db.getLastError()方法是由driver負責調用的,所以業務代碼不需要去顯式調用。這點後面還會專門提到。2、driver一定會調用db.getLastError()函數,但是並不一定能捕獲到錯誤。這主要取決於write concern的設置級別,這也是本文的主題。

write concern:0(Unacknowledged)

此級別調用的時序圖如下:

driver調用了getLastError()之後,mongod立刻返回結果,然後才實際進行寫操作。所以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)

此級別調用的時序圖如下:

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

write concern:1 & journal:true(Jounaled)

此級別調用的時序圖如下:

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)

這個級別只在replica set的部署模式下生效

這個級別下,只有secondary從primary完成了複製之後,getLastError()的結果才會返回。也可以同時設置journal:true或j:true,則還要等journal寫入也成功後才會返回。但是注意,只要primary的journal寫入就會返回,而不需要等待secondary的journal也寫入。類似的也可以設置w:3,表示至少要有3個節點有數據;或者w:majority,表示>1/2的節點有數據。一般小規模的集群就是3節點部署,所以配置w:2就可以了

建議

設置write concern級別,其實就是在寫操作的性能和可靠性之間做權衡。寫操作的等待時間越長,可靠性就越好。對於非關鍵數據,建議使用默認的w:1就可以了,對於關鍵數據,則使用w:1 & j:true比較好。這裡要注意,journal無論如何都是建議打開的,設置j:true,只是說driver調用getLastError()之後是否要等待journal寫入完成再返回。並不是說不設置j:true就關閉了server端的journal。

關於getLastError()

一般來說,開發者寫的代碼,不需要自行調用db.getLastError()函數,driver在每一個寫操作之後,都會立刻自動調用該方法

<code>db.collection("test", {}, function (err, collection) {

collection.insert({name: "world peace"}, function (err, result) {

assert.equal(null, err);

db.close();
})
}); /<code>

這段代碼,driver在insert()之後,隱式調用db.getLastError(),如果捕獲到任何錯誤,就會賦給回調函數中的err參數。區別就在於是否能夠捕獲到錯誤。在w:-1時,err永遠是null(沒有機會捕獲到error);在w:0時,一般也捕獲不到,除了network error;在w:1時,如果mongod apply發生錯誤,就會傳遞給err參數了。代碼都是一樣的,區別就在於設置的write concern級別。