在 MongoDB 副本集架构中,默认的读写行为很容易被误解:写操作确实只能发给主节点,但读操作并不是只能发给主节点。通过 readPreference 参数,客户端可以决定把查询请求发送到哪些节点。这个参数看起来简单,实际会和复制延迟、网络拓扑、业务一致性要求互相影响。正确配置 readPreference 可以显著降低主节点压力,错误配置则可能读到过期数据,甚至引发性能抖动。

readPreference 的全称是读偏好,它属于驱动层或连接层配置,并不会改变副本集本身的数据复制规则。MongoDB 官方驱动在连接到副本集时,会定期获取拓扑信息,根据 readPreference 的值筛选出符合条件的节点,然后在其中按照一定策略选择目标节点。下面先拆解五种模式,再讨论标签路由和配置方法。
一、readPreference 五种模式分别适合什么场景
MongoDB 提供了五种 readPreference 模式,命名上高度对称:primary、primaryPreferred、secondary、secondaryPreferred、nearest。它们的区别主要体现在两个维度:是否允许读从节点,以及从节点不可用或延迟不满足要求时是否回退主节点。
primary 模式是默认值,所有读请求都发往主节点。这种模式一致性最高,读到的永远是最新写入的数据,但主节点会承载全部读写压力。primaryPreferred 模式优先读主节点,只有在主节点不可用时才读从节点,适合希望故障转移但日常仍以主节点为主的场景。
secondary 模式则完全相反,读请求只发从节点,主节点不参与读。它适合主节点写入压力极大、可以接受一定数据延迟的查询,例如离线报表、数据导出或后台任务。secondaryPreferred 模式优先读从节点,如果所有从节点都不可用,则回退到主节点读取,兼顾了读分离和可用性。
nearest 模式不区分主从,驱动会测量到各个节点的网络延迟,选择延迟最低的节点执行读操作。它适合对延迟极其敏感、对一致性要求不高的场景,比如读取地理位置信息、推荐内容等。但要注意 nearest 选择的节点可能是主节点也可能是从节点,而且从节点延迟可能较高,可能读到较旧的数据。
二、readPreferenceTags 如何实现更精细的节点选择
readPreferenceTags 是一个可选配置,它允许在副本集节点打上自定义标签,例如机房位置、机架、硬件类型。客户端在根据 readPreference 筛选出候选节点后,会再按照标签集合进行匹配,实现更细粒度的读路由。比如可以优先读同机房的从节点,减少跨机房网络延迟。
标签需要在副本集配置中定义。启动或修改节点配置时,可以在成员文档中加入 tags 字段,例如 { dc: "east", rack: "r1" } 这样的形式。配置完成后,驱动就能识别这些标签,并根据 readPreferenceTags 的设置进行匹配。
readPreferenceTags 的值是一个有序的标签文档数组。客户端会从数组的第一个标签文档开始匹配,如果能找到至少一个节点完全匹配该标签集合,就只从这些节点中读取;如果没找到,则尝试下一个标签文档,直到匹配成功或使用空文档 {} 表示任意节点。比如先尝试 dc 为 east 的节点,再退而求其次选择 dc 为 west 的节点,最后允许任意节点。
这种机制在多数据中心部署时非常有用。可以把读请求优先路由到本地从节点,避免跨地域读取带来的额外延迟;当本地节点不可用时,再自动回退到其他区域。需要注意的是,readPreferenceTags 对 primary 和 primaryPreferred 模式影响有限,因为这两种模式主要围绕主节点,而标签对主节点不一定生效,具体行为取决于驱动实现。
三、在驱动和连接字符串中正确配置 readPreference
不同语言的 MongoDB 驱动都提供了 readPreference 选项,配置方式略有差异,但核心参数名一致。以 Node.js 官方驱动为例,可以在创建 MongoClient 时传入 readPreference 字符串,也可以使用 MongoDB 连接字符串的查询参数直接指定。两种方式效果相同,后者在环境变量或配置中心里更灵活。
下面先看一个完整的 Node.js 示例。这个例子使用 secondaryPreferred 模式,让查询优先走从节点,从节点全部不可用时回退到主节点,同时设置了连接池大小。
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017/?replicaSet=rs0';
const client = new MongoClient(uri, {
readPreference: 'secondaryPreferred',
maxPoolSize: 10
});
async function run() {
await client.connect();
const db = client.db('shop');
const orders = db.collection('orders');
// 读操作会优先从从节点读取,从节点不可用时回退主节点
const docs = await orders.find({ status: 'paid' }).limit(10).toArray();
console.log(docs.length);
await client.close();
}
run().catch(console.dir);
如果希望在连接字符串中配置,只需在副本集主机列表后面追加 readPreference 参数,多个参数之间用 & 符号分隔。比如下面的 URI 同时指定了 readPreference=secondaryPreferred 和 readPreferenceTags=dc:east。注意在代码中展示连接字符串时,& 需要转义,实际使用的 URI 中仍然是 & 符号。
mongodb://db1.ipipp.com,db2.ipipp.com,db3.ipipp.com/?replicaSet=rs0&readPreference=secondaryPreferred&readPreferenceTags=dc:east
Python 驱动的配置方式类似,在 MongoClient 中传入 read_preference 即可。例如 pymongo 使用 ReadPreference.SECONDARY_PREFERRED 常量,连接字符串同样支持 readPreference 查询参数。Java 驱动则通过 MongoClientSettings 构造,不同语言差异不大,关键是理解模式含义。
四、常见误区与排查方法
第一个常见误区是认为只要设置了 secondaryPreferred,读请求就一定不会打到主节点。实际上当所有从节点不可用或者因为网络分区导致驱动无法获取从节点拓扑时,secondaryPreferred 会自动回退到主节点。如果业务严格要求必须读从节点,应该使用 secondary 模式,并接受从节点全不可用时读操作失败的风险。
第二个误区是把 nearest 简单理解为物理距离最近。Nearest 模式基于驱动周期性测量的网络延迟来选择节点,并受到本地阈值限制,延迟够近的多个节点都可能被选中,而延迟最小的节点不一定总是物理距离最近。因此 nearest 并不能保证读到的数据是某个固定节点上的,也不保证不选主节点。
第三个误区是忽略从节点复制延迟。MongoDB 主从复制是异步的,从节点数据可能落后主节点几毫秒到几秒,甚至在高写入压力下落后更多。当使用 secondary 或 secondaryPreferred 时,应用必须能够容忍这种延迟。如果某些查询不能接受旧数据,应使用 primary 模式,或者在业务上通过写入后短时间读主节点来规避。
排查读偏好配置是否生效,可以查看驱动日志或者使用 MongoDB 的 serverStatus 和当前操作命令,观察连接目标和实际执行的节点。也可以通过 explain 结果中的 serverInfo 查看查询实际在哪个节点执行。如果发现读请求仍然大量落在主节点,需要检查连接字符串参数是否正确、驱动版本是否支持 readPreferenceTags,以及拓扑信息是否更新及时。
MongoDB readPreference读写偏好副本集读取修改时间:2026-08-21 11:18:19