根本原因是客户端使用单地址连接(如mongodb://192.168.1.10:27017)未启用副本集发现机制,导致驱动无法感知主节点变更;必须使用含全部种子节点和replicaSet参数的连接字符串才能自动路由写操作至当前primary。

“Not primary while writing”不是配置错误或权限问题,而是客户端明确连到了 secondary 节点并发起写操作——MongoDB 内核直接拒绝,不走鉴权、不看角色、不查网络,纯架构级拦截。
为什么 rs.status() 显示有 primary,但写还是报错
常见现象是 rs.status() 确实返回了 "state": 1 的节点,但应用仍连到 secondary 并报错。根本原因在于:客户端连接的是静态 IP 或单个 host,没启用副本集发现机制。
- 用
mongodb://192.168.1.10:27017这种单地址连接,驱动不会主动感知主节点变化,哪怕该节点刚降为 secondary - 连接字符串必须包含全部成员(或至少一个 seed),并带上
?replicaSet=rs0参数,才能触发自动发现和路由 - Kubernetes Service 或负载均衡器直连单个 Pod IP,会绕过 replicaSet 协议层的主节点感知逻辑
检查并修正写入策略的三步实操
不依赖“重试”或“手动切 host”,从连接源头堵住问题:
- 确认连接字符串格式正确:
mongodb://node1:27017,node2:27017,node3:27017/?replicaSet=rs0&readPreference=primary—— 缺少replicaSet参数即退化为单节点模式 - 在驱动初始化时显式设置写关注:
writeConcern: { w: "majority" },避免因写入未同步就返回成功而掩盖路由错误 - 禁用任何硬编码 secondary 地址的写逻辑,比如日志收集脚本里写死
mongo --host sec-node-01,这种命令行直连必报NotMaster
readPreference=primary 是默认值,但别信它
很多开发者以为只要没显式设 secondary 就安全,其实不然:
- Node.js 驱动中
readPreference默认确实是primary,但它只影响读;写永远强制发往 primary,不读这个参数 - 真正决定写路由的是连接是否在 replicaSet 模式下建立——这由连接字符串里的
replicaSet参数触发 - 用
mongosh手动连 secondary 时执行db.collection.insertOne({}),哪怕加了--readPreference primary也照样报错,因为连接本身不在 replicaSet 上下文
最容易被忽略的部署陷阱
不是代码写错了,而是环境层绕过了副本集协议:
- 容器编排中用 Headless Service + DNS SRV 记录时,若未在连接串里配
srvServiceName,驱动无法解析完整拓扑 - 某些监控 Agent(如 Prometheus MongoDB Exporter)默认直连 local node,若部署在 secondary 容器里,其健康检查写入就会失败
- 本地开发用 Docker Compose 启动副本集,但应用
localhost:27017连的是 host 网络映射的某一个固定容器,而非通过replicaSet发现机制


















