不能直接用于生产级实时通知。Change Streams 仅提供变更事件流,无重试、确认、去重和投递保障,需自行持久化 resumeToken、实现幂等通知及合理监听策略。

Change Streams 能否直接用于生产级实时通知?
不能直接用。Change Streams 本身只是变更事件的流式管道,不带重试、确认、去重、投递保障——它更像 Kafka 的 consumer group,不是消息队列。你看到的 db.collection.watch() 返回一个游标,一旦连接断开或应用崩溃,未处理的事件就丢了,MongoDB 不保存“已读位点”。
常见错误现象:CursorNotFound 错误频发、部分更新丢失、服务重启后漏掉中间变更。
- 必须自己维护
resumeToken并持久化(例如存到 Redis 或专用 collection) - 每次成功处理一条变更后,才更新
resumeToken;失败时不更新,下次从同一点重拉 - 避免在
watch()回调里做耗时操作(如发 HTTP 请求),否则会阻塞游标心跳,触发超时
如何保证每条变更只通知一次?
Change Streams 不提供 exactly-once 语义,需应用层补足。核心是“先落库,再发通知”,且通知动作必须幂等。
典型做法:把待通知事件写入一张 notifications_queue 表(带 status: "pending" 和唯一 event_id),再由独立 worker 拉取并调用下游接口;成功后更新 status 为 "sent"。
-
event_id建议用变更事件里的operationType + documentKey._id + clusterTime拼接,避免重复生成 - worker 查询时加
{ status: "pending", createdAt: { $lt: new Date(Date.now() - 5 * 60 * 1000) } }防止卡死任务堆积 - 下游服务收到通知后,应基于
event_id做业务层去重,不能依赖 MongoDB 保证
监听哪些集合?要不要用 allChangesForCluster?
除非你有跨库强一致通知需求,否则别用 allChangesForCluster。它要求副本集开启 majority 读写,且事件里不带 database 名,解析成本高,还可能被无关库变更淹没。
更实际的做法:按业务域拆分监听目标,例如用户服务只 watch users 和 user_profiles,订单服务只 watch orders 和 payments。
- 监听单个 collection 时,
watch()调用开销小,恢复快,resumeToken粒度细 - 聚合 pipeline 过滤要前置——比如只关心
status字段从"created"变成"shipped",就用[{ $match: { "fullDocument.status": "shipped", "documentKey._id": { $exists: true } } }] - 避免在 pipeline 里用
$lookup或复杂计算,Change Streams pipeline 只支持有限操作符
为什么本地测试总收不到 insert 事件?
大概率是没开 oplog 或权限不足。Change Streams 依赖副本集的 oplog,单机模式(mongod --port 27017)默认不启用副本集,也就没有 oplog。
必须启动带副本集名称的 mongod:
mongod --replSet rs0 --port 27017
然后初始化:
mongo --port 27017 --eval "rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'localhost:27017' }] })"
- 用户必须有
changeStream权限(如clusterMonitor角色),普通read权限不够 - 驱动版本要匹配:Node.js 驱动需 ≥ 3.6,Python PyMongo ≥ 3.7,旧版会静默降级为 tailing oplog
- 首次 watch 可能延迟 1–2 秒,因为要等待第一个 oplog 条目写入,不是 bug
真实部署时,oplog 大小和保留时间直接影响故障恢复窗口——如果断连 1 小时,但 oplog 只存 30 分钟变更,resumeAfter 就会失效,只能全量重同步。











