mongodb 控制读策略,还有一个 <code>readpreference</code> 的设置,为了避免混淆,先简单说明下二者的区别。
<code>primary</code> 只从 primary 节点读数据,这个是默认设置
<code>primarypreferred</code> 优先从 primary 读取,primary 不可服务,从 secondary 读
<code>secondary</code> 只从 scondary 节点读数据
<code>secondarypreferred</code> 优先从 secondary 读取,没有 secondary 成员时,从 primary 读取
<code>nearest</code> 根据网络距离就近读取
<code>local</code> 能读取任意数据,这个是默认设置
<code>majority</code> 只能读取到『成功写入到大多数节点的数据』
<code>readpreference</code> 和 <code>readconcern</code> 可以配合使用。
<code>readconcern</code> 的初衷在于解决『脏读』的问题,比如用户从 mongodb 的 primary 上读取了某一条数据,但这条数据并没有同步到大多数节点,然后 primary 就故障了,重新恢复后 这个primary 节点会将未同步到大多数节点的数据回滚掉,导致用户读到了『脏数据』。
当指定 readconcern 级别为 majority 时,能保证用户读到的数据『已经写入到大多数节点』,而这样的数据肯定不会发生回滚,避免了脏读的问题。
需要注意的是,<code>readconcern</code> 能保证读到的数据『不会发生回滚』,但并不能保证读到的数据是最新的,这个官网上也有说明。
有用户误以为,<code>readconcern</code> 指定为 majority 时,客户端会从大多数的节点读取数据,然后返回最新的数据。
实际上并不是这样,无论何种级别的 <code>readconcern</code>,客户端都只会从『某一个确定的节点』(具体是哪个节点由 readpreference 决定)读取数据,该节点根据自己看到的同步状态视图,只会返回已经同步到大多数节点的数据。
mongodb 要支持 majority 的 readconcern 级别,必须设置<code>replication.enablemajorityreadconcern</code>参数,加上这个参数后,mongodb 会起一个单独的snapshot 线程,会周期性的对当前的数据集进行 snapshot,并记录 snapshot 时最新 oplog的时间戳,得到一个映射表。
最新 oplog 时间戳
snapshot
状态
t0
snapshot0
committed
t1
snapshot1
uncommitted
t2
snapshot2
t3
snapshot3
只有确保 oplog 已经同步到大多数节点时,对应的 snapshot 才会标记为 commmited,用户读取时,从最新的 commited 状态的 snapshot 读取数据,就能保证读到的数据一定已经同步到的大多数节点。
关键的问题就是如何确定『oplog 已经同步到大多数节点』?
primary 节点
secondary 节点在 自身oplog发生变化时,会通过 replsetupdateposition 命令来将 oplog 进度立即通知给 primary,另外心跳的消息里也会包含最新 oplog 的信息;通过上述方式,primary 节点能很快知道 oplog 同步情况,知道『最新一条已经同步到大多数节点的 oplog』,并更新 snapshot 的状态。比如当t2已经写入到大多数据节点时,snapshot1、snapshot2都可以更新为 commited 状态。(不必要的 snapshot也会定期被清理掉)
secondary 节点
secondary 节点拉取 oplog 时,primary 节点会将『最新一条已经同步到大多数节点的 oplog』的信息返回给 secondary 节点,secondary 节点通过这个oplog时间戳来更新自身的 snapshot 状态。
使用 <code>readconcern</code> 需要配置<code>replication.enablemajorityreadconcern</code>选项
只有支持 readcommited 隔离级别的存储引擎才能支持 <code>readconcern</code>,比如 wiredtiger 引擎,而 mmapv1引擎则不能支持。