change streams 只能在 mongodb 4.0+ 的副本集或分片集群上运行,单机模式直接报错 changestreamnotenabled;需初始化副本集、授予 changestream 角色权限,并手动处理断连重试与流销毁。

Change Streams 只能在 MongoDB 4.0+ 的副本集或分片集群上工作,单机模式(standalone)直接报错 ChangeStreamNotEnabled —— 这是第一个必须确认的硬性前提。
确认 MongoDB 部署模式与权限
Change Streams 依赖 oplog,而 standalone 实例默认不启用 oplog。即使你用 mongod --replSet rs0 启动单节点副本集,也必须执行 rs.initiate() 初始化,否则驱动连接后调用 collection.watch() 仍会抛出 MongoServerError: Change stream is not supported in this configuration。
权限方面,用户需具备 changeStream 角色(推荐绑定到数据库级):
db.runCommand({
createUser: "appuser",
pwd: "secret",
roles: [
{ role: "read", db: "mydb" },
{ role: "changeStream", db: "mydb" }
]
})
- 不要只给
readWrite—— 它不隐含changeStream权限 - 若监听整个数据库变更,用
db.watch(),对应用户需在 admin 数据库拥有changeStream角色 - Node.js 驱动(v4.15+)对权限错误返回的 message 字段可能被截断,重点看 error code 是否为
20(Unauthorized)
正确创建 watch() 并处理断连重试
collection.watch() 返回的是一个 ChangeStream 对象,它本身不是 Promise,也不自动重连 —— 断网、主从切换、cursor 超时(默认 30 分钟)都会导致流关闭,必须手动监听 close 和 error 事件并重建。
典型错误写法是只监听 data:
const stream = collection.watch();
stream.on('data', doc => console.log(doc)); // ❌ 没有兜底,断连即静默失效
正确做法:
根据 package.json 中的 engines 字段自动检测 Node.js 版本需求,未安装 nvm 时自动安装并切换至对应版本。支持 Windows、macOS、Linux。适用于 Node.js 项目开发、版本切换和环境配置。
- 始终监听
error:常见ChangeStreamHistoryLost表示 resume token 失效,需清空 token 重新开始 - 监听
close:触发后应延时重试(避免雪崩),例如用setTimeout(() => startWatch(), 1000) - 传入
{ resumeAfter: token }选项实现断点续传,token 来自上次成功接收的doc._id - 避免在
data回调里做耗时操作(如发 HTTP 请求),否则会阻塞流读取,导致 cursor 过期
过滤变更类型与字段投影
Change Streams 默认推送所有变更(insert/update/delete/replace),但你可以用聚合管道语法过滤:
collection.watch([
{ $match: {
'operationType': { $in: ['insert', 'update'] },
'fullDocument.username': { $exists: true }
}
},
{ $project: { 'fullDocument.password': 0 } }
])
注意几个关键点:
-
fullDocument仅在 update 操作中存在,且需开启fullDocument: 'updateLookup'选项,否则为null -
$project不能排除_id或operationType等元字段,否则驱动报错InvalidPipelineOperator - 正则匹配(
$regex)在$match中可用,但性能较差;高频场景建议用documentKey(含_id)做精确匹配 - filter 是服务端执行的,不增加客户端 CPU 压力,但复杂管道会略微提升 mongod 负载
内存泄漏与流销毁时机
忘记调用 stream.close() 是 Node.js 中最常见的 Change Streams 内存泄漏源 —— 即使流已关闭,未清理的监听器仍驻留内存,尤其在 Express 路由中动态创建 watch 时极易发生。
安全销毁模式:
- 在进程退出前统一调用
process.on('SIGTERM', () => stream.close()) - 若 watch 绑定到某个业务上下文(如 WebSocket 连接),务必在连接关闭时同步
stream.close() - 不要依赖 GC:
ChangeStream内部持有底层 TCP socket 引用,GC 不会自动释放 - 验证是否泄漏:用
process.memoryUsage().heapUsed在反复启停 watch 后对比,持续上涨即有问题
真正麻烦的从来不是怎么启动监听,而是怎么确保它在各种异常路径下都被干净关闭 —— 很多线上事故都源于某次部署回滚时残留的未关闭流占满连接数。










