MongoNotPrimaryException 先于事务失败出现,表明驱动因心跳超时判定主节点失联,根本原因是 Docker 网络延迟导致副本集心跳检测失败,进而触发主节点降级。

事务失败前先报 MongoNotPrimaryException?先盯住心跳超时
事务在 Docker 里跑着跑着就崩,错误日志里却没看到事务相关报错,反而先冒出 MongoNotPrimaryException —— 这说明驱动已经认为主节点“失联”了,根本没走到事务执行那步。根本原因常是副本集成员间的心跳检测失败,而 Docker 网络延迟正是高频诱因。
副本集靠定期心跳(默认每 2 秒发一次)判断节点存活。Docker 默认 bridge 网络下,容器间通信虽快,但若宿主机负载高、CPU 抢占严重,或启用了 --cpus/--memory 限制,单次网络往返可能卡到 1.5s 以上。一旦连续几次心跳响应超时(默认 heartbeatTimeoutSecs=10),仲裁就会触发主节点降级。
- 用
docker stats观察容器 CPU 利用率是否长期 >90%,内存是否频繁触达 limit - 进容器执行
ping -c 5 other_mongo_node和nc -zv other_mongo_node 27017,看丢包和连接建立耗时(>300ms 就值得警惕) - 检查 MongoDB 日志里是否有
"Heartbeat failed"或"No response from heartbeat"字样
mongod 启动参数里漏掉 --replSet 或 --bind_ip_all?副本集直接无法形成
Docker 容器默认只绑定 127.0.0.1,其他副本节点根本连不上,心跳自然全挂。更隐蔽的是:即使加了 --replSet rs0,若没配 --bind_ip_all 或显式列出所有节点 IP,rs.status() 里看到的 members[n].name 可能是容器内部 hostname(如 mongo1:27017),而其他节点解析不到这个域名。
- 启动命令必须包含
--bind_ip_all(或--bind_ip=0.0.0.0),否则外部节点无法建连 - 用
docker run --network=my-repl-network替代默认 bridge,避免 DNS 解析失败 - 初始化副本集时,在
rs.initiate()的 config 里,members[n].host必须填其他容器在该自定义网络里的可解析名(如mongo2:27017),不能填localhost或容器 ID
事务代码里没设 readPreference=primaryPreferred?从节点故障时读请求直接堵死
事务要求所有操作必须落在主节点,但驱动默认的 readPreference 是 primary。一旦主节点因心跳超时被临时踢出,驱动不会自动重试——它就卡在“找不到 primary”上,后续事务根本起不来。而 primaryPreferred 能让读请求在主不可用时 fallback 到从节点,避免阻塞整个连接池。
- Node.js 驱动示例:
new MongoClient(uri, { readPreference: 'primaryPreferred' }) - Java 驱动需在 connection string 加
&readPreference=primaryPreferred - 注意:事务内所有操作仍强制走主节点,这个设置只影响事务外的普通读请求,但它能防止连接池被无效等待耗尽
容器内存限制低于 WiredTiger cache size?OOM 杀进程后副本集状态彻底混乱
WiredTiger 默认把 50% 可用内存分给 cache。Docker 里若设了 --memory=2g,但没调小 wiredTigerCacheSizeGB,MongoDB 启动后 cache 实际会占 ~1GB。一旦查询压力上来,cache 挤占其他内存,Linux OOM Killer 就可能干掉 mongod 进程。进程重启后,副本集状态元数据(如 oplog 截断点)可能损坏,导致节点反复同步失败、选举僵持。
- 启动时显式限制 cache:
command: ["--wiredTigerCacheSizeGB=0.5", "--replSet", "rs0"] - 检查容器内存 limit 是否 ≥ 2× cache size(留足系统和驱动开销)
- OOM 后不要直接
docker start,先docker logs确认是否有"OutOfMemoryError"或"Killed process"字样

















