mongodb 数据一致性

writeconcern

什么事 writeconcern

WriteConcern 决定一个写操作落到多少个节点上才算成功。类似于 MySQL 中的半同步复制。主要保证数据最终一致性 

· 0: 默认值。发起写操作,不关心是否成功;

· 1: 集群最大数据节点数:写操作需要被复制到指定节点数才算成功;

· majority:写操作需要被复制到大多数节点上才算成功。

这里的写入并不是落盘,而是在集群节点的内存中能够查到了;就表示成功了;

默认行为

3 节点复制集不作任何特别设定(默认值)。

image-20220908112741692

w: “majority”

大多数节点确认模式,其中包括主节点

image-20220908113011352

w: “all”

全部节点确认模式

image-20220908113110743

journal 日志

WriteConcern 可以决定写操作到达多少个节点才算成功,journal 则定义如何才算成功。journall 日志类似于 MySQL 中的 redo 日志。是在 writeConcern 基础上对于数据安全的进一步保证。

取值包括:
· true:写操作落到 journal 文件中才算成功;
· false:写操作到达内存即算作成功。

image-20220908113258299

writeconcern 测试

在复制集测试 writeConcern 参数
db.test.insert({count: 1}, {writeConcern: {w: "majority"}}) 
db.test.insert({count: 1], {writeConcern: {w: 3 }} 
db.test.insert({count: 1}, {writeconcern: {w: 4 }}

注意事项

·虽然多于半数的 writeConcern 都是安全的,但通常只会设置 majority,因为这是等待写入延迟时间最短的选择;
·不要设置 writeConcern 等于总节点数,因为一旦有一个节点故障,所有写操作都将失败;
·writeConcern 虽然会增加写操作延迟时间,但并不会显著增加集群压力,因此无论是否等待,写操作最终都会复制到所有节点上。设置 writeConcern 只是让写操作等待复制后再返回而已;
·应对重要数据应用{w:“majority”},普通数据可以应用{w:1}以确保最佳性能。

readPreference 读配置

介绍

ReadPreference 决定使用哪一个节点来满足正在发起的读请求。可选值包括:

· primary:只选择主节点
· primaryPreferred:优先选择主节点,如果不可用则选择从节点 
· secondary:只选择从节点
· secondaryPreferred:优先选择从节点,如果从节点不可用则选择主节点 
· nearest:选择最近的节点

image-20220908120458745

readPreference 场景举例

·用户下订单后马上将用户转到订单详情页-一 primary/ primaryPreferred。因为此时从节点可能还没复制到新订单;
·用户查询自己下过的订单-一 secondary/ secondaryPreferred。查询历史订单对时效性通常没有太高要求;
·生成报表-一 secondary。报表对时效性要求不高,但资源需求大,可以在从节点单独处理,避免对线上用户造成影响;
·将用户上传的图片分发到全世界,让各地用户能够就近读取-一 nearest。每个地区的应用选择最近的节点读取数据。

readPreference 与 Tag

ReadPreference 只能控制使用一类节点。Tag 则可以将节点选择控制到一个或几个节点。、

考虑以下场景:
·一个 5 个节点的复制集
·3 个节点硬件较好,专用于服务线上客户 
·2 个节点硬件较差,专用于生成报表

可以使用 Tag 来达到这样的控制目的
·为 3 个较好的节点打上 { purpose: "online"} 
·为 2 个较差的节点打上 { purpose: "analyse"} 
·在线应用读取时指定 online,报表读取时指定 reporting 
更多信息请参考官方文档:readPreference

image-20220908120731298

readPreference 配置

通过 MongoDB 的连接串参数:
· mongodb://host1:27107,host2:27107,host3:27017/?replicaSet=rs&readPreference=secondary

通过 MongoDB 驱动程序 API:
· MongoCollection.WithReadpreference(ReadPreference readpref) 

Mongo Shell:
db.collection.dind({}).readPref("secondary")

注意事项

·指定 readPreference 时也应注意高可用问题。例如将 readPreference 指定 primary,则发生故障转移不存在primary 期间将没有节点可读。如果业务允许,则应选择 primaryPreferred;

·使用 Tag 时也会遇到同样的问题,如果只有一个节点拥有一个特定 Tag,则在这个节点失效时将无节点可读。这在有时候是期望的结果,有时候不是。例如:

·如果报表使用的节点失效,即使不生成报表,通常也不希望将报表负载转移到其他节点上,此时只有一个节点有报表 Tag是合理的选择,

·如果线上节点失效,通常希望有替代节点,所以应该保持多个节点有同样的 Tag

·Tag 有时需要与优先级、选举权综合考虑。例如做报表的节点通常不会希塑它成为主节点,则优先级应为0。

readconcern 读隔离性保证

什么是 readConcern?

在 readPreference 选择了指定的节点后,readConcern 决定这个节点上的数据哪些是可读的,类似于关系数据库的隔离级别。可选值包括:

· available:读取所有可用的数据;
· local: 读取所有可用且属于当前分片的数据;
· majority:读取在大多数节点上提交完成的数据;
· snapshot:读取最近快照中的数据;
· inearizable:可线性化读取文档;

ReadConcern: local 和 available

在复制集中 local 和 available 是没有区别的。两者的区别主要体现在分片集上。

考虑以下场景:
·一个 chunk x 正在从 shard1 向 shard2 迁移;
·整个 M 迁移过程中 chunk x 中的倍分数据会在 shard1 和 shard2 中同时存在,但源分片 shard1 仍然是 chunk x的负责方:
·所有对 chunk x 的读写操作仍然进入 shard1; 
· config 中记录的信息 chunk x 仍然属于 shard1;
·此时如果读 shard2, 则会体现出 local 和 available 的区别:
    local:只取应该由 shard2 负责的数据(不包括 x);
    available: shard2 上有什么就读什么(包括 x);

注意事项:
·虽然看上去总是应该选择 1ocl,但毕竟对结果集进行过滤会造成额外消耗。在一些无关紧要的场景(例如统计)下,也可以考虑 available;
· MongoDB <=3.6 不支持对从节点使用{ readConcern: "1oca1"},
·从主节点读取数据时黑认 readConcern 是 local,从从节点读取数据时黑认 readConcern 是 available(向前兼容原因)。

image-20220908122217187

ReadConcern: majority

只读取大多数据节点上都提交了的数据。

考虑如下场景:
·集合中原有文档{x:0}; 
·将 x 值更新为 1;

image-20220908122408354

image-20220908122456859

ReadConcern: majority 的实现方式

考虑 t3 时刻的 Secondary1, 此时:
·对于要求 majority 的读操作,它将返回 x=O; 
·对于不要求 majority 的读操作,它将返回 x=ly 如何实观?

节点上维护多个 x 版本,MvCC 机制 MongoDB 通过维护多个快照来链接不同的版本:
·每个被大多数节点确认过的版本都将是一个快照;
·快照持续到没有人使用为止才被删除;

实验:readConcern: " majority”vs“local'”

安装 3 节点复制集。
,注意配置文件内 server 参数 enableMajorityReadconcern
·将复制集中的两个从节点使用 db.fsyncLock() 锁住写入(模拟同步延迟)

image-20220908122625204

ReadConcern 验证

db.test.save({"A": 1})

db.test.find().readConcern("local")

db.test.find().readconcern("major ity") I

·在从节点上执行 db.fsyncUnlock()

结论:
·使用 local 参数,则可以直接查询到写入数据

·使用 majority,只能查询到已经被多数节点确认过的数据

· update 与 remove 与上同理。

ReadConcern: majority 与脏读

MongoDB 中的回滚:

·写操作到达大多数节点之前都是不安全的,一旦主节点前溃,而从节还没复制到该次操作,刚才的写操作就丢失了;
·把一次写操作视为一个事务,从事务的角度,可以认为事务被回滚了。所以从分布式系统的角度来看,事务的提交被提升到了分布式集群的多个节点级别的“提交”,而不再是单个节点上的“提交”。在可能发生回滚的前提下考虑脏读问题 
·如果在一次写操作到达大多数节点前读取了这个写操作,然后因为系统故障该操作回滚了,则发生了脏读问题;使用 { readConcern:“majority"}可以有效避免脏读

ReadConcern:如何实现安全的读写分离

考虑如下场景:

向主节点写入一条数据;立即从从节点读取这条数据。如何保证自己能够读到刚刚写入的数据?下述方式有可能读不到刚写入的订单:

db.orders.insert({ oid: 101, sku: "kite", q: 1]) 
db.orders.find(foid: 101}).readPref ("secondary")

使用 writeConcern+ readConcern majority 来解决:

db.orders.insert({ oid: 101, sku: "kiteboar", q: 1}, {writeConcern: {w: "majority"}}) 
db.orders.find(foid: 101}). ReadPref ("secondary").readconcern ("majority")

image-20220908122935505

读隔离性和 MySQLI 的对比

ReadConcern 主要关注读的隔离性,ACID 中的 Isolation,但是是分布式数据库里特有的概念 
readcocnern: majority 对应于 MysQL 事务中隔离级别中的哪一级?

image-20220908123019662

MongoDBI 的 ACID 事务支持

介绍

MongoDB 虽然已经在 4.2 开始全面支持了多文档事务,但并不代表大家应该毫无节制地使用它。相反,对事务的使用原则应该是:能不用尽量不用。

通过合理地设计文档模型,可以规避绝大部分使用事务的必要性为什么?

事务=锁,节点协调,额外开销,性能影响

MongoDB ACID 多文档事务支持

image-20220908123107260

使用方法

MongoDB 多文档事务的使用方式与关系数据库非常相似:

try  (clientSession clientSession client. StartSession)) { 
clientsession. StartTransaction();
collection. Insertone (clientsession, docone); 
collection. Insertone (clientSession, docTwo); 
clientSession. CommitTransaction ();
}

事务的隔离级别

事务完成前,事务外的操作对该事务所做的修改不可访问
如果事务内使用{ readConcern: “snapshot”},则可以达到可重复读 Repeatable Read

注意事项

可以实现和关系型数据库类似的事务场景必须使用与 MongoDB4.2 兼容的驱动;
事务默认必须在 60 秒(可调)内完成,否则将被取消;
涉及事务的分片不能使用仲裁节点;
事务会影响 chunk 迁移效率。正在迁移的 chunk 也可能造成事务提交失败(重试即可),
多文档事务中的读操作必须使用主节点读;
readConcern 只应该在事务级别设置,不能设置在每次读写操作上。
必须是 wT 引擎才支持事务。