一、介绍

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。