MongoDB的写关注主要指定了写入操作的确认级别。具体来讲就是客户端在向MongoDB执行比如delete、insert、update等相关写入操作时,MongoDB的写入行为,比如是否立刻刷盘、是否需要等待Secondary节点确认等。
写关注配置主要包含以下3个字段:
{ w: , j: , wtimeout: }
以下示例为设置w为3,但实际只有两个节点可写,当写入时,最终等待超时的情况:
PRIMARY> use db
switched to db db
PRIMARY> show tables;
PRIMARY> db.foo.insert({"name":"lisa"},{writeConcern:{w:3,wtimeout:5000}})
WriteResult({
"nInserted" : 1,
"writeConcernError" : {
"code" : 64,
"codeName" : "WriteConcernFailed",
"errInfo" : {
"wtimeout" : true
},
"errmsg" : "waiting for replication timed out"
}
})
PRIMARY> db.foo.find()
{ "_id" : ObjectId("66cc79b22ad2d8af73b13352"), "name" : "lisa" }
可以看到,当数据承载节点少于写关注要求时,会进行超时等待,超时之后MongoDB会返回错误,但实际已经写入到活跃节点的数据不会回滚。
setDefaultRWConcern命令可以设置默认读关注和写关注,设置默认写关注示例,返回结果为修改后的全局读写关注:
test> use admin
switched to db admin
admin> db.adminCommand(
... {
... setDefaultRWConcern : 1,
... defaultWriteConcern: { w:"majority",wtimeout:1000 }, // 全局默认设置
... writeConcern: { w:1,wtimeout:5000 }, // 设置当前会话
... }
... )
{
defaultReadConcern: { level: 'local' },
defaultWriteConcern: { w: 'majority', wtimeout: 10 },
updateOpTime: Timestamp({ t: 1724723294, i: 2 }),
updateWallClockTime: ISODate("2024-08-27T01:48:14.987Z"),
defaultWriteConcernSource: 'global',
defaultReadConcernSource: 'implicit',
localUpdateWallClockTime: ISODate("2024-08-27T01:48:14.987Z"),
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1724723294, i: 3 }),
signature: {
hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
keyId: Long("0")
}
},
operationTime: Timestamp({ t: 1724723294, i: 3 })
}
使用getDefaultRWConcern命令查看默认读写关注
admin> db.adminCommand({"getDefaultRWConcern": 1})
{
defaultReadConcern: { level: 'local' },
defaultWriteConcern: { w: 'majority', wtimeout: 10 },
updateOpTime: Timestamp({ t: 1724723294, i: 2 }),
updateWallClockTime: ISODate("2024-08-27T01:48:14.987Z"),
defaultWriteConcernSource: 'global',
defaultReadConcernSource: 'implicit',
localUpdateWallClockTime: ISODate("2024-08-27T01:48:14.987Z"),
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1724723578, i: 1 }),
signature: {
hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
keyId: Long("0")
}
},
operationTime: Timestamp({ t: 1724723578, i: 1 })
}
或者在副本集配置中查看副本集的写关注确认节点数量:
test> conf=rs.config()
test> conf.settings.getLastErrorDefaults
{ w: 1, wtimeout: 0 }
通过上面的介绍我们知道写writeConcern的w选项指定了写入操作需要等待确认的副本集成员个数;j选项指定了写入操作是否需要等待日志刷盘。那么在w设置为1、j设置为false的情况下肯定是效率最高的,但是也是最容易丢失数据的;当w设置为当前数据承载节点个数、j设置为true时数据是最安全的,因为每次写入都需要确认所有的节点都已经完全写入,不会发生数据丢失或回滚的情况,但是只要其中一个节点发生故障,整个集群的写入就会发生问题。所以我们通常建议将w设置为majority,j设置为true来平衡数据安全性和性能。当然,对于不同业务场景也可以进行更灵活的设置,比如,如果我们的数据全都是一些不是很重要的日志数据,即使数据丢失一部分也没有太大的关系,哪我们的目标就是只用追求性能就好了。
通过上面的SQL演示示例可知,当数据承载节点小于写关注要求时,数据写入就需要等待超时,我们知道,在PSA架构下,仲裁节点是不参与承载数据的,只参与选举操作,那么假设我们为了保证数据安全性将w选项设置为majority,即写入操作需要得到两个承载数据节点的写入确认,如果我们的Primary节点发生了故障,这时候仅有Secondary从节点就会被选举为Primary,但是由于失去了一个数据承载节点,此时仅存的新Primary数据节点已经不能满足写关注的majority要求,就会产生写入等待超时的问题,可以此时业务写入就出现了问题,也就是说整个结构就完全失去了高可用功能。当然,PSA架构还不仅仅只有这一个问题,但是从这个角度来讲,PSA架构已经不适合我们在生产环境中使用。