Capped Collection是MongoDB中预分配固定大小、自动覆盖最旧文档的环形缓冲区式集合,适合事件广播因其支持tailable cursor实现低延迟实时监听、高写入吞吐、无索引开销且免维护滚动清理。

什么是Capped Collection,它为什么适合事件广播?
Capped Collection 是 MongoDB 中一种固定大小的集合,写入时自动按插入顺序覆盖最老文档。它天然支持 tailable cursor —— 一种可“挂起等待新数据”的游标,这正是事件广播的核心机制:服务启动后监听集合末尾,新事件一写入就立刻被拉取。
它比普通集合更适合广播场景,因为:
- 不需要手动维护游标位置(如
_id或时间戳),tailable cursor自动跟踪 - 无索引开销(默认只有
_id索引),写入吞吐高 - 滚动覆盖避免无限增长,省去定时清理逻辑
但要注意:Capped Collection 不支持 update、delete 和任意排序,只适合“只追加 + 只读消费”的事件流。
如何创建并正确配置 Capped Collection?
创建必须在集合首次写入前完成,且不能通过 ORM(如 MongoEngine)直接声明,得用 PyMongo 原生命令:
from pymongo import MongoClient
<p>client = MongoClient("mongodb://localhost:27017/")
db = client["eventbus"]</p><h1>必须显式创建,size 单位是字节,max 是文档数(二者选一,size 优先)</h1><p>db.create_collection(
"events",
capped=True,
size=1024 <em> 1024 </em> 50, # 50MB
max=10000 # 或最多 10000 条(超出则按时间覆盖)
)</p>
关键点:
-
size和max不能同时为 0;生产环境建议以size为主,避免小文档导致过早截断 - 创建后不可修改为非 capped,也不可修改
size/max - 集合名不要带特殊字符或空格,否则
tailable cursor在某些驱动版本中会静默失败
如何用 tailable cursor 实现可靠事件监听?
PyMongo 的 find 需配合 cursor_type=CursorType.TAILABLE_AWAIT 和合理超时,否则容易断连或漏事件:
from pymongo import CursorType
import time
<p>coll = db["events"]
cursor = coll.find(
{}, # 通常不加过滤,广播所有事件
cursor_type=CursorType.TAILABLE_AWAIT,
oplog_replay=True, # 兼容旧版驱动,新驱动可省略
no_cursor_timeout=True # 防止服务器端游标过期
)</p><p>while cursor.alive:
try:
doc = next(cursor)
print("收到事件:", doc)</p><h1>处理事件(注意:此处不应阻塞太久,否则游标可能超时)</h1><pre class="brush:php;toolbar:false;">except StopIteration:
# 暂无新数据,await 期间可能被唤醒
time.sleep(0.1)
except Exception as e:
print("游标异常:", e)
break # 实际中应重连并重新获取 cursor
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
常见陷阱:
- 忘记设
no_cursor_timeout=True,导致游标几秒后自动关闭,监听中断 - 在
next(cursor)外层没包try/except,一次网络抖动就退出整个循环 - 用
find().sort(...)—— Capped Collection 不支持除$natural外的排序,会报错CommandNotSupportedOnCapped
微服务部署时怎么避免多个实例重复消费?
Capped Collection 本身不提供消费组语义,tailable cursor 是“所有监听者都看到全部事件”。若需广播(每个实例都处理)—— 直接起多个监听器即可;若需负载均衡(仅一个实例处理)—— 它就不适用,该换 Kafka 或 Redis Stream。
但很多场景其实是“广播 + 各自状态隔离”,比如:
- 所有订单服务实例同步更新本地缓存
- 所有通知服务实例各自触发短信/邮件
这时要确保:
- 每个微服务实例使用独立的
MongoClient(别共享连接池),否则一个实例崩溃可能影响其他实例的游标 - 不依赖游标“从某条开始”,因为 Capped Collection 可能已覆盖旧数据;新实例启动时只能从当前最新位置开始,丢弃历史事件是设计预期
- 如果业务要求至少一次投递,得在事件处理成功后写入另一个集合做幂等记录,Capped Collection 本身不保证
Capped Collection 的边界很清晰:它不是消息队列替代品,而是轻量级、低延迟、无状态广播管道。一旦你需要重试、死信、精确一次语义或跨服务协调,就得切换技术栈。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










