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)。

    

相关推荐