本节摘要:MongoDB 的核心进程是 mongod,配合 mongo/mongosh 客户端与 WiredTiger 存储引擎工作。本节从一次"升级服务器没升级驱动"的上线事故讲架构组件与版本兼容规则,并给出读日志的入口。
某团队把服务器从 4.0 升到 6.0,应用侧的旧驱动没动。上线当晚,日志里刷出大量 Unsupported OP_QUERY 与命令不识别错误,写入全部失败。根因:MongoDB 5.1 起移除了旧版 OP_QUERY 协议,老驱动只会说"旧方言"。
这条事故引出的架构图景:
(RAM - 1GB) 的一半,这个数字在第 10 章的缓存驱逐事故里会再次出现。| 服务器版本 | 最低驱动要求 | 备注 |
|---|---|---|
| 4.4 | 各语言驱动需升到当年版本 | 移除旧扫描方式 |
| 5.1+ | 需支持 OP_MSG 协议 | OP_QUERY 下线 |
| 6.0/7.0 | 官方推荐最新驱动 | mongosh 全面取代 mongo |
💡 版本升级检查清单里,"应用驱动版本"要和"服务器版本"放在同一行。只升一头的升级等于没升。
// mongosh 连接后先看身份 db.version(); db.getServerStorageEngine(); // 确认 wiredTiger db.hello(); // 查看实例角色,复制集时代更常用
排障时先看 mongod 日志(配置项 systemLog),启动失败、慢操作、选举事件全在里面。日志是 JSON 行格式,字段 s 表示严重级别,c 表示组件,按这两个字段过滤效率最高。
这次事故的定位过程值得完整走一遍。晚上十点切换流量,两分钟后应用日志开始刷异常,错误信息有两类:一类是连接直接被断开,一类是发送命令后收到 Unknown command。运维同学先怀疑网络,抓包看连接建立正常,TCP 层没问题;再看 mongod 日志,发现服务器端记录的是协议层面的拒绝。到这一步方向才转对:客户端发出的请求使用旧版 OP_QUERY 操作码,服务器在 5.1 之后只接受 OP_MSG。
为什么测试环境没拦住?测试环境的应用连接的是另一个还没升级的 4.0 实例,等于升级后的服务器从头到尾没被真实流量打过。这是兼容性事故的典型模式:环境不一致掩盖了协议断层。
修复动作很快:把 Node.js 驱动从 2.x 升到 5.x,Java 驱动同步升级,重发后错误消失。但复盘时算了一笔账——如果升级顺序倒过来,先升驱动再升服务器,旧驱动同样会有兼容窗口。正确做法是查驱动发布说明里的兼容矩阵,选一个双向兼容的版本做中转,先升驱动、观察、再升服务器。
# 升级前先做协议与能力核对 mongosh --quiet --eval 'db.version()' # 6.0.12 mongosh --quiet --eval 'db.hello().maxWireVersion' # 21 ← 驱动支持的 wireVersion 必须不高于此值 node -e 'const d=require("mongodb"); console.log(d.WireDescription?.maxWireVersion ?? "查驱动文档")'
maxWireVersion 是驱动与服务器协商协议版本的机制:驱动报自己支持的版本,服务器取交集。两边差一个大版本通常还能通信,差两个大版本就开始出现"命令不识别"这类模糊报错,这也是为什么升级跨度不要超过一个大版本,4.0 直升 6.0 本身就踩了红线——官方升级路径是 4.0 → 4.2 → 4.4 → 5.0 → 6.0,逐版本升并设置 clusterCompatibilility 观察一个周期。
把 WiredTiger 单独拿出来讲,因为它决定了三个日常能感知的现象。第一是并发控制:文档级并发意味着两个写操作只要不碰同一个文档就互不阻塞,这在关系库行锁直觉之外还多了一层"集合内高并发写入不排队"的体验。第二是压缩:默认 snappy 压缩,磁盘占用通常是内存数据体积的一半以下,对日志类集合换成 zstd 能再省三成,代价是 CPU 略升。第三是检查点机制:引擎每分钟或 journal 满 2GB 做一次检查点,把脏页刷盘,检查点之间的崩溃恢复靠 journal 重放——所以 journal 所在磁盘的写入延迟直接决定写吞吐上限。
// 观察存储引擎行为的三条常用命令 db.serverStatus().wiredTiger.cache; // 关注 "bytes currently in the cache" 与 "tracked dirty bytes" // 缓存占用接近 max 与 eviction 频率飙升,就是第 10 章缓存驱逐事故的前兆 db.serverStatus().wiredTiger.concurrentTransactions; // read/write available tickets,归零说明并发槽耗尽 db.stats().storageEngine; // { name: "wiredTiger" }
(RAM - 1GB) 的一半,调优时绕不开;s 与 c 字段过滤。