变更流事件丢失主因是resumeToken未正确更新或持久化,尤其在服务重启时;需用Redis等可靠存储原子保存并恢复token,捕获异常重建游标,8.0+需关注事务标识字段及showExpandedEvents配置。

变更流事件丢失不是网络抖动或超时导致的,绝大多数情况是 resumeToken 没有被正确更新或持久化 —— 尤其在服务重启、异常中断或跨进程恢复时,resumeAfter 传入了过期或无效的 token。
为什么 resumeToken 不更新就丢事件
MongoDB 变更流靠每个事件的 _id 字段(即 resumeToken)做断点续传。如果代码没把最新 _id 写入可靠存储(比如 Redis 或本地文件),下次启动时只能从旧位置开始,中间所有变更全部跳过。
-
tryNext()在 v6.21+ 驱动中才保证自动更新resumeToken;老版本必须手动调用cursor.resumeToken获取最新值 - 监听循环里没捕获
MongoServerSelectionError等连接异常,会导致游标静默关闭,后续事件不再触发 - 没设置
maxAwaitTimeMS或设得过大,会让单次next()卡住太久,错过心跳或重连窗口
Python 中正确保存和恢复 resumeToken 的写法
不能只依赖内存变量,必须落盘且原子写入。推荐用 Redis 存储,键名带集合名和时间戳避免冲突:
import redis r = redis.Redis() # 每次成功处理一个事件后更新 r.setex(f"cf:mydb.mycoll:resumetoken", 3600, str(event["_id"]))
- 写入前先
json.dumps(event["_id"], default=bytes_to_hex),因为_id是 BSON ObjectId 或复杂嵌套对象 - 启动时用
r.get(...)读取,若存在则传给collection.watch(resume_after=token);不存在就用start_after=None(从当前时间起监听) - 不要用
resume_after=token+full_document="updateLookup"组合,某些 MongoDB 版本下会因 oplog 截断导致 token 失效
监听循环必须包含的错误兜底逻辑
变更流不是“一直运行”的长连接,而是按批次拉取 + 心跳维持的协议。任何一次 next() 抛异常都可能中断整个流:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
- 捕获
MongoNetworkTimeoutError和MongoServerSelectionError后,要重建watch()游标,并重新传入最新resume_after - 遇到
OperationFailure错误码 133,说明resumeToken已失效(oplog 被轮转清理),此时应清空存储的 token 并从头开始(或根据业务容忍度跳过) - 不要在
for event in cursor:循环里直接break或return,否则resumeToken来不及保存
注意 MongoDB 8.0+ 的 clustertime 与事务事件差异
8.0 开始,同一 clustertime 下的多个变更事件未必属于同一个事务,仅靠时间戳无法聚合。真正标识事务的是 lsid + txnNumber 组合字段:
- 如果你在做事务级幂等或状态机,必须同时校验这两个字段,而不是只看
clustertime -
collection.watch(full_document="updateLookup")在 8.0 上对跨分片事务可能返回不完整文档,建议改用showExpandedEvents=True+ 解析operationType和updateDescription - 8.0 新增的
createIndexes、dropIndexes等事件默认不返回,必须显式开启showExpandedEvents=True,否则看起来像“丢了事件”
最常被忽略的点:变更流本身不保证 exactly-once,resumeToken 是“至少一次”语义。业务层必须自己实现去重(比如用 _id 或 lsid+txnNumber 做唯一键判重),不能指望驱动或服务器帮你拦住重复事件。

















