readpreference 在分片集群中单独使用无效,因其仅控制副本集内节点选择,不决定分片路由;需结合 tag-aware sharding、readpreferencetags 和 writeconcern 实现地理就近读写。

为什么 readPreference 单独用在分片集群里不管用
因为 MongoDB 分片集群的读策略最终由 mongos 路由层决定,而 readPreference(比如 nearest 或 secondaryPreferred)只控制「从哪个副本集节点读」,不控制「从哪个分片读」。跨数据中心场景下,你真正需要的是让请求落到地理上就近的分片——这得靠 tag-aware sharding 配合 readPreferenceTags 和 writeConcern 共同实现。
常见错误现象:readPreference: "nearest" 在多数据中心部署中仍可能把读请求发到远端机房的 mongos,甚至路由到异地分片——因为它只看节点 ping 延迟,不看分片标签和物理位置。
- 必须先给每个分片设置
tags(如{"dc": "shanghai"}),再在mongos连接时显式声明readPreferenceTags -
readPreferenceTags是数组,支持多级匹配(如[{"dc": "shanghai"}, {"dc": "beijing"}]),但顺序影响 fallback 行为 - 如果所有 tag 都不匹配,且没设
readPreference: "primary",请求会直接失败,不是静默降级
怎么给分片打标签并绑定到 shard key 范围
Tag-aware sharding 的核心不是“自动识别机房”,而是人工定义分片与标签的映射,并把数据按 shard key 范围绑定到指定标签。这个过程不可逆,一旦数据写入,改 tag 不会自动迁移已有 chunk。
使用场景:你有 3 个分片,分别位于上海、北京、深圳,希望用户 ID 尾号为 0–2 的数据只存上海分片,3–6 存北京,7–9 存深圳——就得用 sh.addShardTag() + sh.addTagRange()。
- 先用
sh.addShardTag("shard0000", "shanghai")给分片打标(shard0000是分片名,不是主机地址) - 再用
sh.addTagRange("db.collection", { "_id": 0 }, { "_id": 3 }, "shanghai")绑定范围;注意边界是左闭右开,{ "_id": 3 }表示“小于 3” - 标签名和范围值类型必须严格匹配
shard key类型(比如ObjectId就不能用字符串边界) - 执行后不会立刻迁移,需等 balancer 下次触发或手动
sh.moveChunk()
readPreferenceTags 和 writeConcern 怎么配合用才不丢数据
只设 readPreferenceTags 只能保读就近,写操作默认仍发往 primary,可能跨机房。要真正实现“本地读写”,写也得约束——但直接设 w: "majority" 会卡住,因为异地节点网络延迟高,majority 很难满足。
性能影响明显:在三机房各 1 主 1 从部署下,若强制 w: {"dc": "shanghai"},意味着写必须被上海机房内多数节点(至少 2 个)确认,否则超时。这对跨机房网络抖动极其敏感。
- 写约束用
writeConcern: { w: "majority", wtimeout: 5000, j: true }是基础,但真正起作用的是自定义w值,比如{ w: {"dc": "shanghai"}, wtimeout: 3000 } - 这个自定义
w必须和分片标签一致,且对应机房内至少有 2 个可投票节点(即votes: 1),否则永远达不到 majority -
readPreferenceTags必须和writeConcern的 tag 保持一致,否则出现“写进上海,却从北京读不到最新数据”的 stale read - 应用连接字符串里加
&readPreferenceTags=[{"dc":"shanghai"}]&w={"dc":"shanghai"}是常见做法,但注意 driver 版本兼容性(4.0+ 稳定支持)
最容易被忽略的两个配置点
一是 mongos 自身没配 localThresholdMS,导致它把上海和北京的 mongos 都当成“近”的,随机选一个转发,完全绕过你的 tag 策略;二是分片副本集里没关 slaveDelay,结果 tag 匹配成功了,但 secondary 数据落后 30 秒,读出来就是脏的。
-
mongos启动时加--localThresholdMS 15(单位毫秒),让它只把延迟 ≤15ms 的分片视为“本地”,避免跨机房误判 - 检查每个分片的副本集成员配置:
rs.conf().members[n].slaveDelay必须为0,否则readPreferenceTags匹配到的 secondary 无法保证实时性 - 所有配置变更后,务必用
sh.status()检查tags是否生效、chunk distribution是否符合预期,别只信日志










