mongo-读操作事务

发布于 — 2020 年 04 月 14 日
#Mongo

读取数据的过程需要关注以下两个问题:

  • 从哪里读?关注数据节点位置
  • 什么样的数据可以读?关注数据的隔离性 第一个问题是由readPreference来解决 第二个问是题由readConcern来解决

什么是readPreference

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

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

readPreperfence使用场景举例

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

readPreference与Tag readPreference只能控制使用一类节点。Tag则可以将节点选择控制到一个或多个节点。 考虑以下场景: 一个5个节点的复制集,3个节点硬件较好,专用于服务线上客户,2个节点硬件较差,专用于生成报表。

可以使用Tag来达到这样的控制目的:

  • 为3个较好的节点打上{purpose:“online”}

  • 为2个较差的节点打上{purpose:“analyse”}

  • 在线应用读取时指定online,报表读取时指定reporting

    https://raw.githubusercontent.com/liunaijie/images/master/20200329100110.png

readPreference配置

  • 通过MongoDB的连接串参数mongodb://host1:port,host2:port,host3:port/?replicaSet=rs&readPreperence=secondary
  • 通过MongoDB驱动程序API MongoCollection.withReadPreference(ReadPreference readPref)
  • Mongo Shelldb.collection.find({}).readPref("secondary")

实验

锁定写入(同步)的命令db.fsyncLock(),解锁:db.fsyncUnlock()

  1. 主节点写入{x:1},观察该条数据在各个节点均可见
  2. 在两个从节点分别执行db.fsyncLock()来锁定写入
  3. 主节点写入{x:2},在各个节点观察数据变化
  4. 接触从节点锁定,观察各节点数据变化

注意事项

  • 指定readPreference时也应注意高可用问题,利用将readPreference指定primary,则发生故障转移不存在primary期间将没有节点可读。所以如果业务允许,则应该选择primaryPreferred
  • 使用Tag时也会遇到同样的问题,如果只有一个节点拥有一个特定Tag,则在这个节点失效将无节点可读。这在有时候是期望的结果,有时候不是。例如
    • 如果报表使用的节点失效,即使不生成报表,通常也不希望将报表负载转移到其他节点上,此时只有一个节点有报表Tag是合理的选择
    • 如果线上节点失效,通常希望有代替节点,所以应该保持多个节点有同样的Tag
  • Tag有时需要与优先级,选举权综合考虑。例如做报表的节点通常不会希望它成为主节点,则优先级应为0

什么是readConcern

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

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

readConcern:local和avaliable 在复制集中local和avaliable是没有区别的。两者的区别主要是体现在分片集上考虑以下场景:

  • 一个chunk x正在从shard1向shard2迁移
  • 这个迁移过程中chunk x中的部分数据会在shard1和shard2中同时存在,但源分片shard1仍然是chunk 想的负责方
    • 所有对chunk x的读写操作仍然进入shard1
    • config中记录的信息chunk x仍然属于shard1
  • 此时如果读shard2,则会体现出local和avaliable的区别:
    • local:只去应该有shard2负责的数据(不包括x)
    • avaliable:shard2上有什么就读什么(包括xß)

注意事项:

  • 虽然看上去总是应该选择local,但毕竟对结果集进行过滤会造成额外消耗。在一些无关紧要的场景下,也可以考虑avaliable
  • MongoDB <=3.6不支持对从节点使用{readConcern:“local”}
  • 从主节点读取数据默认readConcern是local,从从节点读取数据是默认readConcern是avaliable(向前兼容)

readConcern:majority

只读取大多数据节点上都提交了的数据。考虑如下场景:

  • 集合原有文档{x:0}
  • 将x值更新为1

https://raw.githubusercontent.com/liunaijie/images/master/20200329105211.png

这时在各个节点上应用{readConcern:“majority”}来读取数据

Untitled

在t3时,P收到S1写入成功的响应,再加上自己已经完成写入,达到大多数数据节点的条件。此时返回数据。 在t5时,S1收到P向S1发送的消息,此时在S1上达到大多数数据节点的条件,返回数据。

readConcern:majority实现方式

节点上维护多个版本,MVCC机制 MongoDB通过维护多个快照来链接不同的版本

  • 每个被大多数节点确认过的版本都将是一个快照
  • 快照持续到没有人使用为止才被删除

使用条件

如果要使用readConcern:"majority"则必须在配置文件中将此项打开,默认是关闭的:

replcation:
    replSetName: rs0
    enableMajorityReadConcern: true

readConcern:majority与脏读

MongoDB中的回滚

  • 写操作达到大多数节点之前都是不安全的,一旦主节点崩溃,而从节点还没复制到该次操作,刚才的写操作就丢失了
  • 把异常写操作视为一个事务,从事务的角度,可以认为事务被回滚了

所以从分布式系统的角度来看,事务的提交被提示到分布式集群的多个节点级别的“提交”,而不再是单个节点上的“提交” 在可能发生回滚的前提下考虑脏读问题:

  • 如果一次写操作到达大多数节前读取了这个写操作,然后因为系统故障该操作回滚了,则发生了脏读问题

使用{readConcern:“majority”}可以有效避免脏读

readConcern:majorit对于事务隔离级别中的Read Committed