MongoDB是开源文档型NoSQL数据库,它的数据模型灵活,具有高扩展性、高可用性、易用性等特点,能够存储半结构化的数据,并且有丰富的查询语言和索引类型,当前MongoDB已广泛的用在各企业的核心业务系统中。MongoDB也是db-engines排名最高的非关系型数据库。
图片来源:db-engines
在MongoDB读取数据主要是受read concern(读策略)、read preference (读偏好设置 )两个参数控制,其中readconcern决定在读取副本集和分片集数据时的一致性和隔离性,而readpreference 决定客户端驱动读取哪个数据节点的数据。它们的配合使用,可以提高MongoDB 集群的性能,以及在数据一致性和读性能上做平衡。
readconcern 一致性读策略
Readconcern 主要解决脏读问题,从3.2版本后开始支持。比如PSA集群,用户从 MongoDB 的 primary 上读取数据后,这条数据并没有同步从数节点,然后 primary 就故障了。此时不同的Readconcern值,MongoDB 返回数据的处理方式是不同的。
Readconcern有几个不同的参数,分别是local、available、majority、linearizable、snapshot ,数据库在这些参数下的一致性是由弱到强递增的。
◀几种模式介绍▶
- Local
表示读取的数据从实例中返回数据,但不保证数据是否被持久化(即可能被回滚)。该参数默认值为local。
- Available
表示读取的数据从实例中返回数据,但不保证数据是否被持久化(即可以回滚)。乍一看available和local没有啥区别,对于副本集架构两者是相同的,主要区别场景是分片群集。在分片集群下,数据迁移会出现孤儿文档(orphaned document),available模式可以从延迟最低的节点获取数据,而local则直接返回数据。该参数是3.6版的新功能。
- Majority
表示读取返回多数副本集成员已确认的数据,这个数据是持久化的不会被回滚。需要注意,在Majority下只能保证读到的数据“不会发生回滚”,但并不能保证读到的数据一定是最新的,官方也明确做了说明。
Regardless of the read concern level, the most recent data on a node may not reflect the most recent version of the data in the system。
- linearizable
线性读取数据。根据官方资料翻译成中文:该查询返回的数据,反映了在读取操作开始之前完成的所有成功的多数确认写入。查询可能会等待并发执行的写操作传播到大多数副本集成员,然后返回结果。也就是在这种模式下,读可能需要等待其他写操作完成。
- snapshot
从最新的快照中读取数据。如果事务不是因果一致的会话的一部分,并且数据的写入参数writeconcern 值也是majority下,那将从多数提交数据的快照中读取数据。
一般在生产推荐配置成Majority,这种模式是在数据安全和性能上相对平衡的选择,但是使用Majority也有要求和问题。首先它只支持WiredTiger引擎,其次需要写入参数writeconcern 也是majority 才会生效,最后在Majority下也不能完全保证解决了脏读问题。
◀Majority 实现▶
MongoDB 在readconcern majority 下,数据库会起一个单独的snapshot 线程,周期性的对当前的数据集进行 snapshot,并记录 snapshot 最新 oplog的时间戳,得到一个映射表。
最新 oplog 时间戳 | snapshot | 状态 |
t0 | snapshot0 | committed |
t1 | snapshot1 | uncommitted |
t2 | snapshot2 | uncommitted |
t3 | snapshot3 | uncommitted |
当 oplog 同步到大多数节点时,对应节点的 snapshot 才会标记为 commmited,用户读取时,从最新的 commited 状态的 snapshot 读取数据,就能保证读到的数据一定已经同步到的大多数节点。那如何判断oplog 已经同步到大多数节点?
对于primary来说,当secondary 节点的oplog发生变化时,会通过命令将 oplog 进度立即通知给 primary,同时节点间的心跳消息里也会包含最新 oplog 的信息。这样primary 节点能很快知道数据是否已经同步到大多数节点的,并更新 snapshot 的状态。比如当t2已经写入到大多数据节点时,snapshot1、snapshot2都可以更新为 commited 状态。
对于secondary 节点来说,在拉取 oplog 时,primary 节点会将“最新的数据已同步到大多数节点的”的信息返回给 secondary 节点,然后secondary 节点通过这个oplog时间戳来更新自身的 snapshot 状态。
readpreference 读偏好设置
MongoDB 读控制策略除了readconcern策略外,还有readpreference 。它主要控制数据库客户端驱动从哪个节点读取数据。这个特性可以方便地实现读写分离、就近读取等策略。
readpreference 是由三部分组成,分别是mode、maxStalenessSeconds 、tag set,其中mode支持五种类型,分别是:primary、primaryPreferred、Secondary、secondaryPreferred、nearest,我们先看几种模式的具体含义。
◀几种模式介绍▶
- primary
默认模式。读操作只在主节点,如果主节点不可用,报错或者抛出异常。这种策略适用于应用程序需要严格的一致性,但可用性不是首要考虑因素的情况。
- primaryPreferred
大多情况下读操作在主节点,如果主节点不可用,如故障转移,读操作在从节点。
- secondary
仅从secondary节点中读取,如果secondary节点不可用,读将会报错。
- secondaryPreferred
大多情况下读操作在从节点,特殊情况(如没有从节点)读操作在主节点。
- nearest
根据指定的延迟阈值,随机地从符合条件的数据节点中读取操作,不管该节点是主还是从节点。
◀maxStalenessSeconds▶
MongoDB 3.4 以后版本新增maxStalenessSeconds参数。集群的从节点可能因为网络阻塞、磁盘吞吐低、长时间执行等原因,使从节点落后于主节点。当从节点延迟时间超过了该参数定义的值,客户端不会从该节点读取数据。maxStalenessSeconds 不能与primary 模式兼容,只能在其他四种模式下使用。
当选择了使用该参数控制读取数据,客户端会通过比较从节点和主节点的最后一次写时间来估计从节点的过期程度。客户端会把连接指向小于等于maxStalenessSeconds的从节点。另外,需要注意maxStalenessSeconds最小值是90秒,如果小于该值将报错。
You must specify a maxStalenessSeconds value of 90 seconds or longer: specifying a smaller maxStalenessSeconds value will raise an error.
◀标签集▶
如果一个复制集中的成员有tag,就可以通过下面的办法读取到带有具体标签的成员上。例如,如果某个节点有这样的成员标签:
{ "region": "South", "datacenter": "A" }
那么以下tag set可以将读操作指到上述成员(或具有相同标记的其他成员):
[ { "region": "South", "datacenter": "A" }, { } ] // Find members with both tag values. If none are found, read from any eligible member.
[ { "region": "South" }, { "datacenter": "A" }, { } ] // Find members with the specified region tag. Only if not found, then find members with the specified datacenter tag. If none are found, read from any eligible member.
[ { "datacenter": "A" }, { "region": "South" }, { } ] // Find members with the specified datacenter tag. Only if not found, then find members with the specified region tag. If none are found, read from any eligible member.
[ { "region": "South" }, { } ] // Find members with the specified region tag value. If none are found, read from any eligible member.
[ { "datacenter": "A" }, { } ] // Find members with the specified datacenter tag value. If none are found, read from any eligible member.
[ { } ] // Find any eligible member.
◀访问案例▶
总结上面的内容,可以通过下面三种方式去定义不同的readpreference策略。
复制集访问方式:
mongodb://db0.test.com,db1.test.com,db2.test.com/?replicaSet=myRepl&readPreference=secondaryPreferred&maxStalenessSecnotallow=150
分片集群方式:
mongodb://mongos1.test.com,mongos2.test.com/?readPreference=secondaryPreferred&maxStalenessSecnotallow=150
带tag的定式:
mongodb://mongos1.test.com/?readPreference=secondaryPreferred&readPreferenceTags=dc:ny,rack:r1&readPreferenceTags=dc:ny&readPreferenceTags=xxx
总结
通过上文介绍,我们知道MongoDB读数据策略,有readconcern和readpreference两个重要的概念。其中readconcern是读数据时的数据一致性级别,它决定了决定读取数据时读到什么样的数据。通常结合可用性和性能,会将readconcern设置为majority。而readpreference决定读哪个节点的数据,主要用于实现读写分离上。另外,MongoDB还提供了其他的配置选项,如写数据策略(writeconcern)这将在后面的文章中介绍。
作者介绍
司马辽太杰是 NineData 工程师。NineData 向企业和个人提供高效、安全的数据库SQL开发、数据库备份、数据复制/迁移/集成、数据对比等能力的产品,它是开箱即用的SaaS服务,可以快速提升企业SQL开发效率,保障企业数据安全。近期,NineData 即将会支持MongoDB、Redis等NoSQL数据库。NineData 官网地址:https://ninedata.cloud。
本文转载自微信公众号「云数据库技术」,可以通过以下二维码关注。转载本文请联系云数据库技术公众号。