MongoDB: 通过ReadConcern 来处理备库一致读的问题-阿里云开发者社区

开发者社区> 数据库> 正文

MongoDB: 通过ReadConcern 来处理备库一致读的问题

简介: 问题描述 MongoDB的写请求写入Primary, secondary从Primary自动获取并且应用oplog来保持和主库的同步, MongoDB 允许用户从Primary 或者 secondary 读取数据(由客户端ReadPreference 决定)。

问题描述

MongoDB的写请求写入Primary, secondary从Primary自动获取并且应用oplog来保持和主库的同步, MongoDB 允许用户从Primary 或者 secondary 读取数据(由客户端ReadPreference 决定)。但读数据可能存在以下问题:

  1. 用户从secondary读,但secondary还没有跟上Primary,导致读取了老数据
  2. 用户从primary读到数据,但在该数据复制到secondary 之前crash 了,导致用户”已经“读到的数据”丢失了“。

解决办法

MongoDB 引入了ReadConcern的概念, readConcern 的原型为:

readConcern: {
   level: "[majority|local|linearizable|available|snapshot]",
   afterOpTime: { ts: <timestamp>, term: <NumberLong> },
   afterClusterTime: <timestamp>,
   atClusterTime: <timestamp>
 }

afterOpTime 已经Deprecated 了,可以忽略。
ClusterTime 暂时理解为一个时间点, 而这个时间点是什么含义取决于level 参数。
afterClusterTime 表示在该时间点之后。
atClusterTime 可能是4.2 的全局事务有关,目前driver 也不支持设置。

如果level 为local, ClusterTime 则表示applied oplog time。
如果level 为majority, ClusterTime 则表示大多数节点都达到的 applied oplog time。

为了解决上述问题1, 用户可以指定local + afterClusterTime, 表示用户要数据 ClusterTime 之后的数据(但依然可能不是最新数据),如果这个节点没有,服务器不要返回,一直等到有为止。

为了解决上述问题2, 用户可以指定level 为majority + afterClusterTime, 表示用户要在ClusterTime之后的数据,并且大多数节点都已经有这个数据了。 这时,可以保证即使原来有该数据的节点死掉,是新的primary肯定有这个数据,所有的secondary最终也会有这个数据。

内部实现

每一个MongoDB slave 都维护了一个 _lastAppliedOpTime 和 _lastCommittedOpTime。 (根据mongodb 的配置,_lastAppliedOpTime 有时应该是_durableOpTime,这里不做区分)

_lastAppliedOpTime 表示本节点applied 到了那个时间,
_lastCommittedOpTime 表示复制集内写入到majority节点的_lastAppliedOpTime。

_lastAppliedOpTime 的值比较容易维护,每次apply一批oplog 就会直接更新。_lastCommittedOpTime 更新比较困难。 主要有以下几种方式:

  1. 通过HeartBeat 更新。 MongoDB 中任何一个节点都会和别的节点进行HB, 在每一个HB 的过程中, 发送响应方都会包含自己的_lastAppliedOpTime。 每次处理HeartBeat response的时候, 会去更新 _lastCommittedOpTime。 但HB 的频率默认是2s, 效率比较低。
  2. 每次自己apply 一些oplog 后,借助于HB的结果, 也会改变_lastCommittedOpTime 的值。 所以本节点的_lastAppliedOpTime 比较快,但其他节点的信息还是有可能2s的间隔。
  3. MongoDB 的每一个slave 应用完一批oplog 后, 会立即将自己的_lastAppliedOpTime告诉primary, primary 需要这个信息对writeConcern == ”Majority“ 的客户进行相应。 所以Primary 有比较实时的全局的 _lastCommittedOpTime 信息。 所以各个slave 只要能够及时从primary 那里获得_lastCommittedOpTime 就可以了。 HB 显然不合适,因为间隔太长。 理论上可以放在
    a). slave update 它的_lastAppliedOpTime 到primary 的时候, primary 返回primary 的_lastCommittedOpTime。

b). slave 每次向它的source节点请求oplog 的时候,source节点会返回它的_lastAppliedOpTime。

上述2种方式都比较及时, MongoDB 4.0 中采用了 b 方案。

因为最新的数据不一定是majority 的数据,而对于一个节点来说,用户要的可能是majority 的数据,所以存储引擎需要有一种保存历史数据的能力,目前只有wiredTiger才支持snapshot,所以如果要使用ReadConcern=="majority", 必须使用wiredTiger 引擎。

使用方法

由第一部分我们看到readConcern 需要设置level, afterClusterTime, atClusterTimeout,MongoDB driver 提供了相关的接口让用户自己设置。 其中的Level 用户使用readConcern 则必须指定。其他的则不是必须, driver 会设置默认值。

如果使用了 causally consistent, MongoDB driver会自动更新afterClusterTime 为上一条请求的response 的operationTime。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章