一、介绍¶
readPreference 主要控制客户端 Driver 从复制集的哪个节点读取数据,这个特性可方便地配置读写分离、就近读取等策略。
Read preference 的构成:
- read preference mode
- a tag set list
- maxStalenessSeconds
- hedged read //可用于 MongoDB 4.4+ 分片集群,用于使用非主读优先级的读取。通过 hedged read,mongos 实例可以将读取操作路由到副本集的 2 个成员进行查询,并且将第一个查询的结果进行响应返回。hedged read 的设置 db.adminCommand( { setParameter: 1, readHedgingMode: "off" } )
read preference mode 可选值包括:
- primary: (只主)只从 primary 节点读数据,这个是默认设置
- primaryPreferred:(先主后从)优先从 primary 读取,primary 不可服务,从 secondary 读
- secondary:(只从)只从 scondary 节点读数据
- secondaryPreferred:(先从后主)优先从 secondary 读取,没有 secondary 成员时,从 primary 读取
- nearest:(就近)根据网络距离就近读取,根据客户端与服务端的 PingTime 实现
注意:
- 除了 primary 模式以外的其他模式可能返回的数据都不是那么实时
- 可通过设置 maxStalenessSeconds 来避免从 secondary 节点读出来的数据过于 "stale"

二、readPreference 场景举例¶
- 用户下订单后马上将用户转到订单详情页——primary/primaryPreferred。因为此时从节点可能还没复制到新订单;
- 用户查询自己下过的订单——secondary/secondaryPreferred。查询历史订单对时效性通常没有太高要求;
- 生成报表——secondary。报表对时效性要求不高,但资源需求大,可以在从节点单独处理,避免对线上用户造成影响;
- 将用户上传的图片分发到全世界,让各地用户能够就近读取——nearest。每个地区的应用选择最近的节点读取数据。
三、readPreference 与 Tag¶
readPreference 只能控制使用一类节点。Tag 则可以将节点选择控制到一个或几个节点。
配置方式:
conf=rs.conf()
conf.members[0].tags = { "region": "South", "datacenter": "A" }
rs.reconfig(conf)
考虑以下场景:
- 一个 5 个节点的复制集
- 3 个节点硬件较好,专用于服务线上客户
- 2 个节点硬件较差,专用于生成报表
可以使用 Tag 来达到这样的控制目的
- 为 3 个较好的节点打上 {purpose: "online"}
- 为 2 个较差的节点打上 {purpose: "analyse"}
- 在线应用读取时指定 online,报表读取时指定 analyse
更多信息请参考官方文档:readPreference
# Without a tag set
db.getMongo().setReadPref(
"secondary", // mode
null, // tag set
{ enabled: true } // hedge options
)
# With a tag set
db.getMongo().setReadPref(
"secondary", // mode
[ { "datacenter": "B" }, { } ], // tag set
{ enabled: true } // hedge options
)

四、readPreference 配置使用¶
通过 MongoDB 的连接串参数:
mongodb://host1:27107,host2:27107,host3:27017/?replicaSet=rs&readPreference=secondary&maxStalenessSeconds=120
通过 MongoDB 驱动程序 API:
MongoCollection.withReadPreference(ReadPreference readPref)
Mongo Shell:
db.getMongo().setReadPref('secondary')
五、readPreference 实验:从节点读¶
mongo --host repl/192.168.1.153:27018,192.168.1.154:27018,192.168.1.155:27018 -uroot -proot123456 --authenticationDatabase admin
mongo --host repl/192.168.1.150:27018,192.168.1.151:27018,192.168.1.152:27018 -uroot -proot123456 --authenticationDatabase admin
1、主节点写入 {x:1},观察该条数据在各个节点均可见
repl:PRIMARY> db.test001.insert( {x:1} )
2、在两个从节点分别执行 db.fsyncLock() 来锁定写入(同步)
repl:SECONDARY> db.fsyncLock()
3、主节点写入 {x:2}
repl:PRIMARY> db.test001.insert( {x:2} )
4、查看主库和从库的信息
#设置从主库查询
repl:PRIMARY> db.getMongo().setReadPref(
"primary", // mode
null, // tag set
{ enabled: true } // hedge options
)
repl:PRIMARY> db.test001.find()
{ "_id" : ObjectId("635936f8174ab4f875c4dc83"), "x" : 1 }
{ "_id" : ObjectId("63593716174ab4f875c4dc84"), "x" : 2 }
#设置从从库查询
repl:PRIMARY> db.getMongo().setReadPref("secondary")
repl:PRIMARY> db.test001.find()
{ "_id" : ObjectId("635936f8174ab4f875c4dc83"), "x" : 1 }
5、解除从节点锁定
repl:SECONDARY> db.fsyncUnlock()
6、查看
repl:PRIMARY> db.getMongo().setReadPref("secondary")
repl:PRIMARY> db.test001.find()
{ "_id" : ObjectId("635936f8174ab4f875c4dc83"), "x" : 1 }
{ "_id" : ObjectId("63593716174ab4f875c4dc84"), "x" : 2 }
六、注意事项¶
- 指定 readPreference 时也应注意高可用问题。例如将 readPreference 指定 primary,则发生故障转移不存在 primary 期间将没有节点可读。如果业务允许,则应选择 primaryPreferred;
- 使用 Tag 时也会遇到同样的问题,如果只有一个节点拥有一个特定 Tag,则在这个节点失效时将无节点可读。这在有时候是期望的结果,有时候不是。
- 例如:
- 如果报表使用的节点失效,即使不生成报表,通常也不希望将报表负载转移到其他节点上,此时只有一个节点有报表 Tag 是合理的选择;
- 如果线上节点失效,通常希望有替代节点,所以应该保持多个节点有同样的 Tag;
- Tag 有时需要与优先级、选举权综合考虑。例如做报表的节点通常不会希望它成为主节点,则优先级应为 0。