getLastError
Mongodb的写操作默认是没有任何返回值的,这减少了写操作的等待时间,也就是说,不管有没有写入到磁盘或者有没有遇到错误,它都不会报错。但一般我们是不放心这么做的,这时候就调用getlastError命令,得到返回值。
getLastError 是Mongodb的一个命令,它好像是取得最后一个error,但其实它是Mongodb的一种客户端阻塞方式,用这个命令来获得写操作是否成功的信息。getlastError有几个参数:j,w,fsync。
在多线程模式下读写Mongodb的时候,如果这些读写操作是有逻辑顺序的,那么这时候也有必要调用getlasterror命令,用以确保上个操作执行完下个操作才能执行,因为两次执行的连接有可能是不同的。在大多数情况下,我们都会使用连接池去连接mongodb,所以这是需要注意的。
getlasterror的最佳实践
1、如果没有特殊要求,最低级别也要使用WriterConcern.SAFE,即w=1。
2、对于不重要的数据,比如log日志,可以使用WriterConcern.NONE或者WriterConcern.NORMAL,即w=-1或者w=0,省去等待网络的时间。
3、对大量的不连续的数据写入,如果每次写入都调用getLastError会降低性能,因为等待网络的时间太长,这种情况下,可以每过N次调用一下getLastError。但是在Shard结构上,这种方式不一定确保之前的写入是成功的。
4、对连续的批量写入(batchs of write),要在批量写入结束的时候调用getlastError,这不仅能确保最后一次写入正确,而且也能确保所有的写入都能到达服务器。如果连续写入上万条记录而不调用getlastError,那么不能确保在同一个TCP socket里所有的写入都成功。这在并发的情况下可能就会有问题。避免这个并发问题
5、对数据安全要求非常高的的配置:j=true,w="majority" db.runCommand({getlasterror:1,j:true,w:'majority',wtimeout:10000})
对于getlasterror是否采用,需要考虑业务的具体场景和业务逻辑设计,并不是所有的场景都能适用。以我这次遇到的案例,对于短信类的服务平台,本身对实时性要求非常高,且会调用第三方的短信服务平台,逻辑存在漏洞但是为了保证实时性,数据库层面不建议他进行这样的安全写入机制,业务开发也觉得这种限制不可行。
getLastError返回对象属性
属性 描述
ok 布尔值,返回getLastError命令是否成功完成
err 描述错误的字符串,在最后一个请求没有发生错误时为null
errmsg 2.6版本新功能,errmsg包含了错误的描述,只有在前面操作出现错误时才会出现。
code 最后一次操作的错误代码
connectionId 连接ID
lastOp 在最后一个操作是对副本集车管员的写入或更新操作时,为存储请求修改的oplog中的option时间戳
n 最后一次操作时更新或删除操作时,为更新或删除的文档数
syncMillis 等待写入磁盘操作时花费的毫秒数。
shards 当对一个分片集群进行写入操作时,shards会标识要写入操作的片。只有当写入操作针对多个片时,该参数才会出现。
singleShard 当对一个分片集群进行写入操作时,shards会标识要写入操作的片。如果写入操作只有一个片时,则只能出现singleShard。
updatedExisting 布尔值,如果最后一个操作时更新操作时,且至少影响到一个文档,同时没有导致upsert时,这个属性将为true
upserted 如果最后一个操作为更新请求且导致的时插入,该属性将为插入的文档的ObjectId
wnote 布尔值,如果错误与写入关注有关,则为true
wtimeout 布尔值,返回是否因wtimeout设置而超时,
waited 如果最后一个曹祖音wtimeout设置而超时,则为超时前等待的毫秒数
wtime 最后一次操作完成前等待的毫秒数
writtenTo 如果写入到副本集,则writtenTo是一个数组,该数组包含根据命令中w字段的值确认先前写入操作的成员的主机名和端口号。
在java 里,update,delete 都有writeconcern选项可以设置,这个设置被驱动用来调用getlastError 方法,并返回结果到WriteResult里。
在命令行里,writeconcern 可以直接在insertMany insertOne update 等方法上使用,如果有异常,直接返回。
writeConcern选项
MongoDB支持的WriteConncern选项如下
- w: 数据写入到number个节点才向用客户端确认
- {w: 0} 对客户端的写入不需要发送任何确认,适用于性能要求高,但不关注正确性的场景
- {w: 1} 默认的writeConcern,数据写入到Primary就向客户端发送确认
- {w: “majority”} 数据写入到副本集大多数成员后向客户端发送确认,适用于对数据安全性要求比较高的场景,该选项会降低写入性能
- {w:-1} 忽略网络错误
- {w:2} 要求以写入到副本集的主服务器和一个备用服务器
- j: 写入操作的journal持久化后才向客户端确认
- 默认为”{j: false},如果要求Primary写入持久化了才向客户端确认,则指定该选项为true
- wtimeout: 写入超时时间,仅w的值大于1时有效。
- 当指定{w: }时,数据需要成功写入number个节点才算成功,如果写入过程中有节点故障,可能导致这个条件一直不能满足,从而一直不能向客户端发送确认结果,针对这种情况,客户端可设置wtimeout选项来指定超时时间,当写入过程持续超过该时间仍未结束,则认为写入失败。
{w: “majority”}解析
{w: 1}、{j: true}等writeConcern选项很好理解,Primary等待条件满足发送确认;但{w: “majority”}则相对复杂些,需要确认数据成功写入到大多数节点才算成功,而MongoDB的复制是通过Secondary不断拉取oplog并重放来实现的,并不是Primary主动将写入同步给Secondary,那么Primary是如何确认数据已成功写入到大多数节点的?
- Client向Primary发起请求,指定writeConcern为{w: “majority”},Primary收到请求,本地写入并记录写请求到oplog,然后等待大多数节点都同步了这条/批oplog(Secondary应用完oplog会向主报告最新进度)。
- Secondary拉取到Primary上新写入的oplog,本地重放并记录oplog。为了让Secondary能在第一时间内拉取到主上的oplog,find命令支持一个awaitData的选项,当find没有任何符合条件的文档时,并不立即返回,而是等待最多maxTimeMS(默认为2s)时间看是否有新的符合条件的数据,如果有就返回;所以当新写入oplog时,备立马能获取到新的oplog。
- Secondary上有单独的线程,当oplog的最新时间戳发生更新时,就会向Primary发送replSetUpdatePosition命令更新自己的oplog时间戳。
- 当Primary发现有足够多的节点oplog时间戳已经满足条件了,向客户端发送确认。
writeConcern总结:
1、write concern用于控制写入安全的级别,可以分为应答式写入以及非应答式写入
2、write concern是一个性能和数据强一致性的权衡,应根据业务场景进行设定
3、对于强一致性场景,建议w>1或者等于majority,以及journal为true,否则w=0
4、在副本集的情形下,建议通过配置文件来修改w以及设置wtimeout,以避免由于某个节点挂起导致无法应答
writeConcern 和getLastError 的关系
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的设置级别,这也是本文的主题。
3、写安全机制 实际上就是在 安全性跟性能之间做权衡。
readConcern选项
读取关注允许您控制从副本集和副本集分片读取的数据的实时性,一致性和隔离性。
通过有效使用写入关注和读取关注,可以适当调整一致性和可用性保证的级别,例如等待更强的一致性保证,或者放松一致性要求以提供更高的可用性。
现在读取关注分为了一下几个级别:
读取偏好(Read Preference)
读取偏好描述mongodb客户端如何将读取操作路由到副本集的成员。
默认情况下,应用程序将其读取操作指向副本集中的主节点。
注:
1. 指定读取偏好时要小心:除主节点之外的其他模块可能会返回过时数据,因为使用异步复制时,从节点中的数据可能不会反映最近的写入操作。
2. 读取偏好不会影响数据的可见性;即客户端可以在确认或传播给大多数副本集成员之前看到写入结果: 无论写入问题如何,使用”local” 或”available”读取关注的其他客户端都可以在写入操作被确认给发出客户端之前看到写入操作的结果。 使用”local” 或”available”读取关注的客户端可以读取可能随后回滚的数据。