Mongodb操作與基本特性


本文主要介紹Mongodb中CRUD常用操作、存儲引擎、數據模型,以及如何使用java Driver。



一、BSON

Mongodb中數據存儲格式爲BSON,和JSON非常類似,可以說在整體的結構幾乎一樣,只不過BSON定義了更多的數據類型,這對面向對象編程語言非常友好。

{
"_id": {
"$oid": "55f6c87fdefdd10de72fc024"
},
"name": "zhangsan",
"age": 30,
"is_alive": true,
"addresses": [
"beijing",
"shanghai"
],
"created": {
"$date": 1442236543707
}
} 



文檔格式大概如上所示,如果數據類型時JSON不支持的,那麼數據類型也將寫入文檔中,比如date。BSON中有2個比較特殊的類型,其中date對應java中的Date,array對於java中List。其他類型比如boolean,string,int等與java都一一對應。



在mongodb中,一條數據稱爲一個document,其API類爲org.bson.Document,當然Document也是Bson的子類,同時也實現了Map接口,其內部有一個LinkedHashMap作爲數據支撐(所以mongodb中字段是有順序的)。

Document user = new Document()
.append("name", "zhangsan")
.append("age", 30)
.append("is_alive",true)
.append("addresses", Arrays.asList("beijing","shanghai"))
.append("created", new Date()); 



上述就是創建一個Document的過程,這和操作一個Map其實並沒有太大區別。



二、java driver示例

如下簡單展示如何使用mongodb java客戶端開發,因爲下面的例子都是基於JAVA的。具體參見:“mongodb java”

<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.0.3</version>
</dependency> 


創建MongoClient的方式有多種,可以參看MongoClient的構造方法即可。最終歸結來由2個主要方式,一個是MongClient + MongoClientOptions,另一個是MongClient + conenctionString。例子如下,如果想在spring環境中使用,請你繼續封裝。關於授權登陸,SSL方面,稍後會專門介紹,此處僅爲示例:

MongoClient mongoClient = new MongoClient("127.0.0.1", 27017);
//MongoClient mongoClient = new MongoClient(new MongoClientURI("mongodb://127.0.0.1:27017"));
MongoDatabase db = mongoClient.getDatabase("test");//獲取DB
MongoCollection<Document> collection = db.getCollection("user");//獲取collection(表)
//通過collection進行CRUD操作 


CRUD的例子將會在下文中列舉。



三、Read操作

即從mongodb中讀取一條或者多條數據,可以在query中使用多種複合查詢條件,這種查詢條件的組合類似於SQL,我們也可以使用“projection”過濾器來指定需要返回的結果中包含(不包含)哪些字段,以提升IO效率,因爲Mongodb可以使用衆多的類似於SQL的查詢操作,而且支持多種indexes,這或許就是mongodb的亮點所在。(Mongodb之上的SQL引擎:Drill,即使用sql查詢mongodb數據)
MongoCollection<Document> collection = db.getCollection("user");//獲取collection(表)
//通過collection進行CRUD操作
Bson parent = Filters.eq("name", "zhangsan");
parent = Filters.and(parent,Filters.gt("age", 10));
MongoCursor<Document> cursor = collection.find(parent).sort(new Document("age", 1)).skip(10).limit(5).iterator();
try {
while (cursor.hasNext()) {
Document item = cursor.next();
System.out.println(item.toJson());
}
} finally {
cursor.close();//must be
} 



上面例子就是一個典型的query,我們基本上可以通過多種組合,即可完成複雜的查詢。我們使用了Filters API來構建查詢條件,這是一個便捷的方式。當然開發者也可以自己使用mongodb的“比較指令”來自己封裝:
Document filter = new Document("name",new Document("$eq","zhangsan")); 


自己封裝不僅繁瑣而且易於出錯,建議使用Filters。



1、比較符

mongodb支持如下幾種比較操作,它們均可以在query的查詢條件中:$eq(相等,逐字節比較),$gt(大於),$gte(大於等於),$lt(小於),$lte(小於等於),$ne(不等於),$in(值是否在指定的數組中),$nin(值不在制定的數組中)。

2、邏輯判斷符

$or:邏輯or,如果document匹配多個條件判斷中的一個或者多個,則將此document返回。

$and:邏輯and,document必須匹配全部的判斷條件。

$not:邏輯!,$and取反,如果document不匹配條件,則返回此document。比如:"age":{$not:{$gt:16}}表示“獲取age不大於16的document”,它的內部檢測過程就是“如果此document存在age字段,且大於16的,一律不返回”,言外之意是:如果document中不包含age字段,或者age的值小於等於16的均會返回。

$nor:not or。

3、元素

$exists:檢測文檔中是否存在此字段,比如: "name":{$exists:true}表示如果name字段存在,則返回此文檔。

$type:檢測文檔中字段的類型,比如:"age":{$type:16}表示如果age字段的類型爲int則返回此文檔,參見BSON Types

4、數組

$all:如果字段的值是數組,$all表示此數組中包含指(不一定必須等於)定的所有元素。比如:"addresses":{$all:["beijing","shanghai"]},首先addresses字段值是一個數組,且同時包含“beijing”、“shanghai”兩個元素。

$elemMatch:數組中至少包含一個元素同時滿足條件,比如:"item":{$elemMatch:{$gt:25,$lt:60}},這表示數組item至少包含一個元素大於25且小於60,item:[15,26,70]這個文檔就符合要求,而item:[16,62]就不符合要求。

$size:數組的元素個數滿足條件,比如"item":{$size:2}則會返回item中元素個數爲2的文檔。

此外,我們可以在表達式中指定index位置來獲取數組中的元素:
collection.find(new Document("addresses.0","beijing")) 

表示獲取addresses這個數組中第0個位置的元素值爲“beijing”的文檔。



5、運算與表達式

$mod:取模運算,比如"price":{$mod:[4,0]}即獲取price與4取模爲0的文檔。

$regex:正則匹配,基於PCRE,首先參與匹配的field必須建立索引,否則性能受限。語法樣例爲:{<field>:{$regex:/pattern/,$options:'<options>'}},比如{"name":{$regex:/^liu/,$option:i}}表示查詢去name字段值以“liu”開頭的文檔。其中$regex表示正則表達式,表達式需要以“/”開頭和結尾,$option表示輔助選項,它有4個可選值:“i”表示不區分大小寫;“m”表示“多行匹配”,其中行符爲"\n";“s”表示“.”符號可以匹配所有字符包括換行符,“x”表示忽略空白符。

表達式可以簡寫爲{<field>:{$regex:/pattern/options}}。如果表達式中不包含正則匹配符(比如$,^,*等,參見PCRE),表示字符串完全匹配,而且正則表達式可以在字符串比較操作中直接使用。

比如:"name":/zhangsan/,表示name中包含完整字符串"zhangsan"的文檔,"name":/^zhang/,表示以zhang開頭的文檔。

"name"/^zhang/i,這是正則的簡寫形勢,表示以zhang開頭且不區分大小寫。

"description":"/^S/m",我們指定了選項值爲m,它可以匹配多行字符串,只要每行爲S開頭即可。比如文檔"description":"Fist line \nSecond line"將匹配成功。



6、projection

$:這個符號,很特殊,對於query而言,用於獲數組中匹配比較表達的第一個元素。這是一個“projection”操作,稍後我們會詳解。比如文檔"number":[16,31,6,88],對於描述“如果number中存在30~100的元素,則獲取第一個匹配的元素”,我們可以使用$與elemMatch:
Document filter = new Document()
.append("$gt",30)
.append("$lt",100);
MongoCursor<Document> cursor = collection.find(Filters.elemMatch("number",filter)).projection(new Document("number.$",1)).iterator(); 


結果將輸出:{"number":[31]}。projection表示需要在結果中包含(不包含)那些字段。

projection是mongodb提供了用於過濾返回字段的操作,1表示包含字段,0表示不包含。

$slice:限定返回結果中數組元素的數量,可以只返回數據的起止位置之間的元素。
Document document = new Document("number",new Document("$slice",Arrays.asList(2,5)));
MongoCursor<Document> cursor = collection.find().projection(document).iterator(); 

如上例,對於number數組,只需返回位置2之後的5個元素,默認起始位置爲0,語法爲$slice:[skip,limit]。"number":{$slice:2}則表示返回位置2之後的元素,其中skip可以爲負數,表示從尾部計數,比如$slice:[-20,5]表示從尾部計數第20個開始取5個元素。



7、find()方法:返回一個FindIterable對象,我們通常根據FindIterable獲取一個Cursor,我們就可以遍歷此cursor,依次讀取結果集,FindIterable中有很多方法有用的方法:

1)batchSize(int size):每次網絡請求返回的document條數,比如你需要查詢500條數據,mongodb不會一次性全部load並返回給client,而是每次返回batchSize條,遍歷完之後後再通過網路IO獲取直到cursor耗盡。默認情況下,首次批量獲取101個document或者1M的數據,此後每次4M,當然我們可以通過此方法來覆蓋默認值,如果文檔尺寸較小,則建議batchSize可以大一些。

2)skip(int number)、limit(int number):同SQL中的limit字句,即表示在符合匹配規則的結果集中skip一定數量的document,並最終返回limit條數據。可以實現分頁查詢。

3)maxTime(int time,TimeUnit unit):表示此次操作保持的最長時間,即server端保持cursor狀態的最長時間,如果超時server端將移除此cursor,即在此通過此cursor遍歷數據將會error。

4)sort(Bson bson):根據指定field排序,參與排序的字段最好是索引,如果不是,將會在內存中排序,如果參與排序的數據尺寸大於32M,將會拋出error。1表示正序,-1表示倒敘,比如"age":1表示按照age正序排序。

5)noCursorTimeout(boolean timeout):如果cursor空閒一定時間後(10分鐘),server端是否將其移除,默認爲false,即server會將空閒10分鐘的cursor移除以節約內存。如果爲true,則表示server端不需要移除空閒的cursor,而是等待用戶手動關閉。無論如何,開發者都需要注意,手動關閉cursor。

6)partial(boolean partial):對於sharding集羣,如果一個或者多個shard不可達,是否允許返回部分數據(只從正常的shard中獲取數據)。

7)cursorType():指定cursor類型,當cursor遍歷完畢後是否關閉cursor,默認是關閉,無論何時都建議手動關閉cursor(不管是否耗盡curosr);當然有些開發場景可能需要保持cursor的活性,遍歷到cursor的最後一條後,不關閉cursor,繼續等待,此後一段時間內如果有新數據插入到cursor之後,則可以繼續遍歷,這就是Tailable Cursor,通常對於Capped Collection中使用。目前支持支持3種類型的Cursor:NonTailable、Tailable、TailableAwait。

8)projection(Bson bson):限定返回結果中需要包含的filed或者數組元素。在6)中我們已經看到相關的幾個例子。默認情況下,將會返回document的所有字段,1表示包含,0表示不包含。

 
MongoCursor<Document> cursor = collection.find().projection(new Document("name",0)).iterator();

表示文檔中不包含name字段。
MongoCursor<Document> cursor = collection.find().projection(new Document("address",1)).iterator(); 

表示結果中只包含address字段。通過例子比較,我們會發現“包含”和“不包含”互爲排斥,通常只會在projection使用一種。



8、內嵌文檔:內嵌文檔可以通過"."來表達路徑,比如文檔:
{
"_id": 1,
"addressess":{
"zip_code":100010,
"city":"beijing"
}
} 

可以通過查詢內部文檔:

collection.find(new Document("addresses.zip_code",10010)); 

9、查詢優化:和其他數據庫一樣,mongodb也支持查詢優化,也支持輔助索引,同時_id字段是唯一索引,通常我們需要根據查詢條件,來創建合適的輔助索引,比如在單字段索引或者多個字段的組合索引,索引的作用最終是避免對整個collection(磁盤IO)全量掃描。合適的索引將會匹配較少比例的數據,否則不當的索引事實上並不能有效的篩選數據,比如一個字段status,最多有3個不同的值,那麼對status建立索引並不能提高查詢性能,因爲query根據status不能過濾掉大量數據,這就是“基準”。



覆蓋查詢(Cover query)表示查詢使用了索引,且所有需要返回的字段都是索引的一部分,這意味着只需要索引就可以篩選並獲得結果內容,這一過程均會在內存中進行,無需進行磁盤查詢整個document,這是性能比較高的。不過,如果對數組做索引,即“multi-key index”將不支持覆蓋查詢;如果查詢條件或者projection包含了內置document字段,則也不支持覆蓋查詢。比如文檔:

{"_id":1,user:{login:"testuser"},如果對"user.login"建立索引,那麼如下查詢仍將不會使用查詢覆蓋:


collection.find(Filters.eq("user.login","testuser")).projection(new Document("userlogin",1)) 

不過這個查詢仍然會使用到user.login索引,但不會覆蓋查詢。

在sharding環境中,如果index中不包含shard key,將不支持覆蓋查詢;_id除外,如果查詢中僅用_id查詢且返回結果中只包含_id字段,仍會使用覆蓋查詢。



mongodb也提供了explain,用來分析query的查詢計劃,通常我們可以使用explain來判定此query的性能。具體請參看explain結果分析以及explain介紹。



9、分佈式查詢:mongodb的分佈式模型分爲replica set和sharded cluster。

sharded集羣中將read根據sharding key(分片鍵)轉發到指定的shard節點,read操作非常高效;當然如果query中沒有包含sharding key,那麼此次read將會被轉發到所有的shard節點上,並有mongos server負責merge結果(包括排序),所以這種情況性能較差(俗稱scatter、gather),對於大型集羣,這種查詢通常是不可行的。



對於replica set而言,只是涉及到將read操作路由到哪個secondary上,默認情況下,read請求總是在primary上發生,我們可以通過指定“read preference mode”來調整這一行爲。
collection.withReadPreference(ReadPreference.secondaryPreferred()).find(); 

上述代碼表示儘可能從 secondary上讀取,如果所有的secondary都失效則從primary上讀取。可以實現“讀寫分離”。目前支持的read模式:

1)primary:讀操作只發生在primary上,默認的read模式。如果primary失效,則返回error。

2)primaryPreferred:讀操作發生在primary上,如果primary失效,則由secondary接收read請求。這是一種比較良好的模式,

3)secondary:讀操作只發生在secondary上,如果所欲哦的secondary都失效,則返回error。

4)secondaryPreferred:讀操作通常發生在secondary上,如果所有的secondary都失效,則由primary接收read請求。

5)nearest:讀取“最近”的節點,mongodb客戶端將評估與每個節點的網絡延遲,有限選擇延遲最小的節點,primary和secondary都有可能接收到read請求(不同的Client或許延遲不同)。



四、Write操作

write操作包括insert、update,replace,remove四種類型,需要清楚的是mongodb並不支持事務,所以如果write操作影響多條document,那麼它們之間的變更並非原子性的,即有可能幾條document修改(插入)成功但是其他的或許失敗;對於一個document而言,是原子性的,不可能存在一個documnet被部分更新的情況。

Document document = new Document()
.append("number", Arrays.asList(15, 20, 44, 60, 80));
collection.insertOne(document); 

對於insert操作,mongodb會檢測文檔中是否指定了_id,如果沒有指定,server端將會根據一定的算法(ObjectId方法)生成一個並保存,如果開發者自己指定_id,需要確保全局唯一。



update操作可以根據查詢條件,更新相應的一條或者多條documents。java Driver中提供了updateOne、updateMany、findAndModify等方法。可以在UpdateOptions中指定upsert來實現“如果存在就更新,否則就插入”的語義:
UpdateResult result = collection.updateOne(Filters.eq("name", "zhangsan"),
new Document("$set",new Document("modified", new Date())),
new UpdateOptions().upsert(true));
if(result.getModifiedCount() > 0) {
System.out.println("found and modified!");
} 

replace就是“替換”,將整個文檔,全部替換爲指定的document。



1、$isolated:隔離,這涉及到mongodb lock機制,mongodb允許同時有多個read、write操作交錯執行,當write操作影響到多條記錄時,我們可以使用"$islolated"避免這種情況,此時write操作將持有全局鎖,直到此操作涉及到的所有的document都變更完畢,此期間其他client將不會看到變更的數據,直到此write操作結束或者error退出。isolated將會較長時間的持有lock,會對整體的併發能力帶來負面影響;此外,它並不表達事務中的“all-or-nothing”語義,即如果write操作更新一部分數據之後,其他document失敗了,並不會“回滾”操作,那些成功的document將仍會被保留。isolated在sharded集羣中不生效。

 collection.updateMany(new Document("name", "zhangsan").append("$isolated",1),new Document("$set",new Document("modified", new Date())); 

2、Fields更新:

1)$inc:對字段的值自增,語法格式:{$inc:{<field1>:amount1,<field2>:amount2}},正負數值都可以,負值表示自減,如果指定的filed不存在,則增加此字段且設定爲指定的值。如果字段的值爲null,使用inc操作將會拋出錯誤。

2)$mul:乘法。

3)$rename:更改字段的名稱,語法:{$rename:{<field1>:<newName1>,<field2>:<newName2>}},新字段不能與原字段名一樣,對於內嵌文檔,可以使用“.”表示路徑。如果文檔中已經存在newName,則首先移除那個字段,然後再將指定的Field重命名。如果指定的字段不存在,則什麼都不做。

4)$set:update操作,更新指定字段的值,如果字段不存在則添加此字段和值。

5)$unset:將指定字段從文檔中移除,語法:{$unset:{<field1>:"",....}}。

6)$min:如果指定的值比字段的當前值要小,那麼將字段的值修改爲指定的值。比如{$min:{"score":60}},如果文檔中score的值 > 60,那麼$min將會把score的值修改位60。

7)$max:語義同$min。

8)$currentDate:比較有用的指令,將指定字段的值修改爲當前時間,類型可以爲Date或者Timestamp,默認爲Date,語法爲:{$currentDate:{<field1>:<type>,....}},需要使用到$type。


##使用Date類型,只需要字段值爲true即可,當然也可以使用$type:date
##如果是timestamp類型,需要使用$type
{$currentDate:{
"modified_date":true,
"modified_timestamp":{$type:"timestamp"}
}
}

3、Array操作:

1)$addToSet:將元素添加到指定的數組中,如果數組中已經存在此元素值則不添加。如果此字段不是數組,則操作失敗。如果此字段不存在,則創建此字段且將設定爲指定的數組元素。語法:{$addToSet:{<field>:<item>}},其中item需要是一個單值,如果是數組,需要藉助$each修飾符來拆解。比如:{$addToSet:{<field>:{$each:[item1,item2]}}},此時item1、item2均會分別添加到<field>的數組中。

2)$pop:從數組的頭或者尾部移除一個元素,就像操作雙端隊列一樣。-1表示移除第一個元素,1表示移除最後一個元素。語法:{$pop:{<field>: -1 | 1}}。

3)$pull:從數據中移除符合條件的所有元素,即有條件的移除。語法:{$pull:{<field1}: <value | condition>,<field2>:<value | condition>....},如果指定的是condition,那麼只有某個元素同時滿足所有條件才能被刪除。比如數組{"item"[{"size":10,"color":"read"},[{"size":30,"color":"black"}]},那麼{$pull:{"item":{"size":{$gt:20},"color":"black"}}}將會移除第二個元素。詳細參見“pull”。

4)$push:將元素追加到數組的尾端。語法:{$push:{<field1>:<value1>,<field2>:<value2>,...}},如果需要對一個數組添加多個元素(子數組),那麼需要使用$each修飾符。



4、modifiers(修飾符):

1)$each:可以在$addToSet和$push操作中配合使用,例子參見上。

2)$slice:我們在read操作中已經瞭解了slice可以用來限制返回的數組元素個數,那麼在write操作中,也可以用過更新後,保留最新的num個元素,它需要配合$push和$each一起使用。
#語法
{
$push: {
"field" : {
$each:[item1,item2,item3],
$slice:<num>
} 

其中slice的值爲0表示將數組置爲空,正數表示保留最後num個元素,負數表示保留最前的num個元素。

3)$sort:同$slice一樣,必須配合$push和$each一起使用,表示push之後根據指定的方式對數組進行重排序。1表示正序,-1表示倒序,如果數組元素爲內嵌文檔,可以使用內嵌文檔的字段排序,語法和$slice一樣,只是sort部分可以爲:$sort: <1 | -1>或者$sort: {<field>: <1 | -1>}

4)$position:配合$push和$each一起使用,表示push時將元素列表添加到哪個位置,默認爲原數組的尾部。$position:0表示添加到第0個元素位置(即首個元素之前)。



5、writeConcern:

Write connern是mongodb提供了一種保障機制,當write操作成功後可以獲得通知。mongodb有多個level的擔保,這個有點類似於事務級別。當write操作使用較弱的concern,那麼操作可以更快的返回(阻塞更少的時間),但有可能數據並沒有持久化(寫入磁盤),有丟失的風險;較強的concern需要等待較長的時間但是數據持久能力更強,更不容易丟失。詳細請參考“write concern”

每個write concern包含三個參數:

1)w選項:默認值爲1,即{w:1},即當客戶端發送write操作後等待server返回確認信息,write操作只需要在primary上寫入成功即可;“0”表示不需要向客戶端返回確認信息,即當write操作發送後,客戶端不需要等待server的執行結果,但是網絡異常這種error仍然可以拋出;“majority”表示當write操作在primary執行成功後,並傳播給“大多數”的secondary且執行成功後才返回確認信息,對於replica set架構而言,這種方式是最佳的,可以有效的避免write數據丟失的問題;如果w的值設置成任何>1的值,適合在replica set架構中使用,表示write操作在指定個數的nodes上執行成功後才返回確認信息(包括primary),如果集羣中沒有足夠的nodes在線,則等待足額的節點加入且執行成功後返回。

2)、j選項

j選項用來控制mongod實例將數據寫入磁盤上的journal日誌(注意,是寫入磁盤,直接flush),這可以確保在mongod異常關閉後數據不會丟失,如果此選項設置爲true,則mongod必須開啓journal功能,否則將會報錯。當write操作寫入journal日誌且flush到磁盤後,才向客戶端發送確認消息。

3)、wtimeout選項

指定write操作的耗時,毫秒,僅當w選項的值 > 1時適用。當write操作超時後,將會返回error,或許此write操作最終成功了(比如primary等待secondary的返回信息,此時超時,則返回給客戶端error,不過最終此write可能會在secondary上成功執行,只是還沒有來得及向primary返回成功消息),不過mongodb在timeout之後不會撤銷已經成功的數據更新。如果設置爲0或者沒有設置此值,那麼write操作將會等待直到滿足條件。(可能導致數據不一致)
collection.withWriteConcern(WriteConcern.MAJORITY).updateMany(...) 

6、分佈式寫入操作:

對於sharded集羣中的一個shared collection(即collection允許分片存儲),write操作將會由相應的shard節點負責執行,這由config server來決定。參見“分佈式寫入”。

對於replica set而言,write操作只能有primary負責接收,並異步的方式傳播給secondary,大量的寫入操作可能導致secondary不能及時的跟進,那麼發生在secondary上的read操作可能會讀取到舊的數據,如果primary失效,將會failover其中一個secondary成爲primary,爲了保證讀取一致性也有可能觸發rollbacks(即在primary上寫入成功,但是在secondary上沒有,但是primary失效了,此後primary再次加入,則需要rollback並與secondary保持一致,這也意味着此前的某些write操作丟失)。爲了避免這個問題,開發者需要指定合適“write concern”,以確保足夠多的secondary能夠跟進primary,通過降低write的吞吐量來提高數據一致性(避免網絡分區,CAP),此外還需要server端的一些額外的支持,比如oplog、journal等。



7、write操作性能

如果write操作影響到了索引,那麼還需要對相應的索引進行變更。比如insert操作將會導致索引文件中新增條目,update操作中修改了索引的值也會觸發索引的調整。所以這也是write操作對性能的影響之一。(高效索引設計有個要求,就是最好不要修改索引的值,是一種低效的操作)



有些update可能會導致document的尺寸增長,對於MMAPV1引擎而言,如果文檔大小超過了原來分配的size,那麼mongodb將會重新分配一個連續的磁盤空間來保存此文檔(廢棄以前的文檔空間,可以被其他document重用),這也會需要額外消耗一些性能。在mongodb 3.0之後,MMAPV1引擎默認使用了“Power of 2 Size Allocations”以及自動padding,即爲每個document分配的size爲2的次冪(稍後詳解),剩餘空間被padding,這可以幫助mongodb高效的重用那些刪除文檔而產生的空閒空間,以及在很多情況下可以減少了重新分配空間的可能。

畢竟Mongodb是一個存儲系統,高效的磁盤對提升性能有極大的幫助,影響存儲性能的因素包括:磁盤介質(SSD,HDD)、磁盤緩存、預讀以及RAID配置等。



mongodb通過使用一種稱爲journal的預寫日誌來保證write操作的可靠性,在數據寫入實際的數據文件之前,首先將變更寫入journal(journal日誌是append操作,性能較高;但對應實際的數據文件是隨機寫,性能較低,所以對於數據文件的修改通常是批量 + 延遲寫入)。在write concern中開啓journal選項,這可以保證write操作的持久性,但是會大大降低整體的write吞吐量。我們可以修改mongod的“commitIntervalMs”,即journal日誌flush到磁盤的間隔時間,此值越小,flush的頻率越高,持久能力越強,即丟失數據的可能越低,但是對磁盤的IO卻越大;此值越大,所來帶的問題就是當異常crash時,mongodb丟失數據的風險就越高,因爲上次一次flush之後的write尚沒有被持久寫入journal文件,將可能會丟失。這是一個在性能與可靠性之間的權衡。



8、Bulk write

mongodb支持批量write操作,即多個write操作批量發送到mongod,同樣mongod不會保證這些操作原子性的執行(事務性),只是提高了IO交互的性能。可以將insert、update、remove、replace等多種類型的write批量提交到mongd。
List<WriteModel<Document>> bulks = new ArrayList<WriteModel<Document>>();
bulks.add(new InsertOneModel<Document>(new Document("name","zhangsan")));
bulks.add(new UpdateOneModel<Document>(new Document("name","lisi"),new Document("modified",new Date())));
collection.withWriteConcern(WriteConcern.MAJORITY).bulkWrite(bulks,new BulkWriteOptions().ordered(false)); 

Bulk write可以分爲ordered或者unordered,通過BulkWriteOptions指定。如果設定了有序性,那麼mongod將會依次執行列表中的write操作,如果出錯,mongodb將會返回那些尚未執行的操作列表。對於unordered,mongodb則會將它們並行執行,如果出錯,也將返回沒有執行的操作。由此可見,ordered執行相對較慢,只有其中一個執行成功,纔會執行下一個。



五、Document

mongodb中每條document的最大尺寸爲16MB,對於超過16MB的數據建議使用Gridfs,一個高效的文件系統。我們稍後再介紹GridFS。

上文中我們已經提到,Document是BSON結構,key-value對,java中實現爲LinkedHashMap,mongodb在保存document時,將儘可能保持字段的順序;但是_id字段總是在第一個位置,插入時如果沒有指定_id則由mongodb生成,有些操作可能會影響字段的順序,比如$rename。



六、其他

Replica set:複製集,mongodb的架構方式之一 ,通常是三個對等的節點構成一個“複製集”集羣,有“primary”和secondary等多中角色(稍後詳細介紹),其中primary負責讀寫請求,secondary可以負責讀請求,這有配置決定,其中secondary緊跟primary並應用write操作;如果primay失效,則集羣進行“多數派”選舉,選舉出新的primary,即failover機制,即HA架構。複製集解決了單點故障問題,也是mongodb垂直擴展的最小部署單位,當然sharding cluster中每個shard節點也可以使用Replica set提高數據可用性。



Sharding cluster:分片集羣,數據水平擴展的手段之一;replica set這種架構的缺點就是“集羣數據容量”受限於單個節點的磁盤大小,如果數據量不斷增加,對它進行擴容將時非常苦難的事情,所以我們需要採用Sharding模式來解決這個問題。將整個collection的數據將根據sharding key被sharding到多個mongod節點上,即每個節點持有collection的一部分數據,這個集羣持有全部數據,原則上sharding可以支撐數TB的數據。



系統配置:1)建議mongodb部署在linux系統上,較高版本,選擇合適的底層文件系統(ext4),開啓合適的swap空間 2)無論是MMAPV1或者wiredTiger引擎,較大的內存總能帶來直接收益。3)對數據存儲文件關閉“atime”(文件每次access都會更改這個時間值,表示文件最近被訪問的時間),可以提升文件訪問效率。 4)ulimit參數調整,這個在基於網絡IO或者磁盤IO操作的應用中,通常都會調整,上調系統允許打開的文件個數(ulimit -n 65535)。


轉載自:http://shift-alt-ctrl.iteye.com/blog/2243695

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章