真正只读需三端协同:服务端配置hidden:true+priority:0+slaveDelay、禁用localhost绕过;客户端强制directConnection=true;禁用tags/readonly等无效参数。

不能只靠 rs.add() 加个节点就当它是只读的。真正隔离写入,必须同时控制服务端配置、副本集成员属性和客户端连接方式三端。
rs.add() 时必须设 hidden: true + priority: 0 + slaveDelay
只加 rs.add("report-node:27017") 会让节点变成普通 secondary,它仍能被选为 primary、接受写请求、出现在驱动发现列表里。真实配置要一步到位:
-
hidden: true:节点不会出现在rs.status()的公开成员列表中,mongos 和多数驱动默认跳过它 -
priority: 0:彻底失去选举资格,哪怕 primary 挂了也不会被推举 -
slaveDelay: 3600(单位秒):延迟一小时同步,防误操作数据瞬间污染报表库
完整命令示例:rs.add({ host: "report-node:27017", priority: 0, hidden: true, slaveDelay: 3600 })
节点本地 mongod.conf 必须禁用 localhost 绕过
即使配置了 hidden 和 priority: 0,如果运维或脚本直接连 localhost:27017,仍可能绕过权限控制写入。关键检查项:
- 确认
setParameter: { enableLocalhostAuthBypass: false }显式写在配置文件里(MongoDB 4.2+ 默认关闭,但老版本或自定义镜像可能开着) - 禁止
bind_ip: 0.0.0.0,至少绑定到内网 IP 或127.0.0.1+ 认证强制开启 - 确保 keyfile 权限是
600,且auth: true已启用
客户端必须用 directConnection=true 强制直连
驱动默认会拉取整个 replica set 配置,发现节点 hidden: true 就自动忽略——你配得再严,它根本不会连过去。解决方法只有一个:
- 连接串里加
?directConnection=true(如mongodb://report-node:27017/db?directConnection=true) - Java/Python/Node.js 驱动都支持该参数,且必须显式传
directConnection=True(大小写依语言而定) - 绝对不要混用 replica set 连接串(含多个 host)和这个节点,否则驱动会重新触发发现逻辑
错误示范:readPreference=secondaryPreferred —— 它只从 *可见* 的 secondary 中挑,你的节点被 hidden 了,永远选不上。
别碰 tags 或 readOnly: true 参数
这两个东西对“真只读”完全无效,还容易埋坑:
-
tags只影响 readPreference 路由,不阻止写命令到达节点,也不影响选举;加了{"readonly":"true"}却没设hidden,运维连上去照样db.collection.insert() -
readOnly: true是启动参数,仅限制本机 socket 连接,远程 TCP 连接照常可写,4.2+ 已废弃 -
fsyncLock()是全局写阻塞,会卡住复制线程,报表节点同步停滞,且已弃用
真正只读的关键不是“锁写”,而是让节点既不在选举路径上、也不在驱动发现路径上、更不在运维直连路径上——三路切断,才叫稳。


















