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)
从图可见,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)
和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就可以了。
写入到内存:mongodb执行写入的时候,是首先把数据写入到内存当中,如果在数据写入到日志文件journal或者刷新到硬盘之前mongod挂掉了,那么数据就会丢失。
写入到journal:如果打开journal,那么即使断电也只会丢失100ms的数据,这对大多数应用来说都可以容忍了。从1.9.2+,mongodb都会默认打开journal功能,以确保数据安全。而且journal的刷新时间是可以改变的,2-300ms的范围,使用 --journalCommitInterval 命令。
写入到硬盘:数据刷新到oplog和硬盘的时间间隔是60秒,对于复制来说,不用等到oplog刷新到硬盘,在内存中就可以直接复制到Secondary节点。
getLastError 是Mongodb的一个命令,从名字上看,它看起来好像是取得最后一个error,但其实它是Mongodb的一种客户端阻塞方式。用这个命令来获得当前线程上一个写操作是否成功的信息。
getlastError有几个参数:j,w,fsync。在大多数的语言驱动中,这个命令是被包装成WriteConcern类,比如java。j表示是否需要等待写入完日志再返回成功通知,w表示至少写入到几个服务实例再返回成功通知,fsync表示是否需要等待刷新到硬盘再返回成功通知。
journal无论如何都是建议打开的,设置j:true,只是说driver调用getLastError()之后是否要等待journal写入完成再返回。并不是说不设置j:true就关闭了server端的journal。
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即可。
如果没有特殊要求,最低级别也要使用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的构造如下,想查看详细情况请阅读源码
//w:-1, wtimeout:0, fsync:false, j:false
public static final WriteConcern NONE = new WriteConcern(-1);
//w:0, wtimeout:0, fsync:false, j:false
public static final WriteConcern NORMAL = new WriteConcern(0);
//w:1, wtimeout:0, fsync:false, j:false
public static final WriteConcern SAFE = new WriteConcern(1);
//w:"majority", wtimeout:0, fsync:false, j:false
public static final WriteConcern MAJORITY = new Majority();
//w:1, wtimeout:0, fsync:true, j:false
public static final WriteConcern FSYNC_SAFE = new WriteConcern(true);
//w:1, wtimeout:0, fsync:false, j:true
public static final WriteConcern JOURNAL_SAFE = new WriteConcern(1, 0, false, true);
//w:2, wtimeout:0, fsync:false, j:false
public static final WriteConcern REPLICAS_SAFE = new WriteConcern(2)
运维网声明
1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网 享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com