Node.js应用随机断连MongoDB Elastic Beanstalk超时排查
根因
这个故障是Mongoose连接池泄漏 + 链路中间设备丢弃空闲TCP连接形成半开连接堆积导致的,所有现象都和这个问题完全匹配:
- 非DB请求100%正常:说明EB实例上的Node服务、前置Nginx反向代理本身运行正常,故障仅出在DB连接链路
- 重启立刻恢复:重启应用会完全重置Mongoose连接池,清空所有僵死的无效连接,服务直接恢复
- 本地连接同数据库正常:数据库本身服务无异常,问题仅存在于EB实例到数据库的连接管理逻辑、以及中间网络链路
- 同架构其他应用无故障:仅当前应用存在未正确释放DB连接的代码路径,或是当前应用的流量特征刚好能触发连接池占满的阈值,其余应用要么流量更低,要么没有泄漏连接的逻辑,半开连接数量不足以占满整个连接池,就不会触发全局故障
随机发作、可自行恢复的逻辑也很明确:EB实例和MongoDB之间的AWS NAT网关、安全组状态表默认会静默丢弃静默时长超过300-360秒的空闲TCP连接,不会给两端发送连接断开的通知,Mongoose连接池里就会残留大量实际已经断开的“半开连接”。请求拿到这类连接发起DB操作时,会一直卡到TCP默认超时才会失败,卡住的请求占满所有连接池槽位后,后续所有DB请求都拿不到可用连接,就会全部挂起,Nginx等不到Node返回响应就会抛出你看到的upstream timed out错误。等几分钟后这些僵死请求超时被系统回收,连接池槽位释放,服务就会自行恢复。
排查步骤
- 故障发作时登录EB实例,给Node进程发送
USR1信号打印调用栈:执行kill -USR1 <node进程PID>,栈信息会输出到/var/log/web.stdout.log,搜索Mongoose、MongoClient相关的调用帧,就能定位到是哪段代码拿了连接没有正常释放,90%以上的泄漏都是漏写await的异步DB操作、或是异常分支没有捕获错误导致请求挂起、连接无法归还连接池。 - 检查Mongoose初始化配置,确认
bufferCommands配置状态:这个参数默认开启,会在连接不可用时把所有DB请求缓存在内存里,一旦连接僵死就会无限制堆积请求,不会触发快速失败。 - 对比Nginx的
proxy_read_timeout配置、Node侧接口超时配置、Mongoose的socket超时配置,确认不存在Mongoose超时时间长于Nginx超时的情况——这种场景下Nginx已经主动断开了客户端请求,但Node侧还在等待DB响应,连接会一直被占用无法释放。 - 逐行对比出问题应用和其他4套正常应用的代码差异,重点排查:绕开Mongoose直接用原生Mongo驱动操作连接的逻辑、长事务/Change Stream/游标未主动关闭的逻辑、Express中间件中提前return但未等待DB操作结束的逻辑、未加await的Mongoose调用。
修复方案
- 调整Mongoose连接初始化参数,从配置层面避免半开连接堆积、连接无限卡住的问题:
mongoose.connect(DB_CONNECTION_STRING, { maxPoolSize: 50, // 单t3.small/t3.medium实例设置50-100足够,不要盲目调大 minPoolSize: 5, heartbeatFrequencyMS: 30000, // 每30秒发送一次连接心跳,避免空闲连接被中间设备静默丢弃 socketTimeoutMS: 10000, // 所有socket操作10秒超时,禁止无限等待 connectTimeoutMS: 10000, // 新连接建立10秒超时 serverSelectionTimeoutMS: 5000, // 选择可用服务器超时5秒,快速识别僵死节点 bufferCommands: false, // 关闭命令缓冲,连接异常时直接报错,不要在内存堆积请求 })
- 给Mongoose连接加全局事件监听,把连接异常、断开、重连的事件全打到日志里,方便后续定位故障触发的时间点和关联操作:
const db = mongoose.connection; db.on('error', (err) => console.error('Mongoose connection error:', err)); db.on('disconnected', () => console.warn('Mongoose connection dropped')); db.on('reconnected', () => console.log('Mongoose connection restored'));
- 给Express加全局错误处理中间件,确保所有DB操作抛出的异常都能被捕获,请求无论成功失败都能正常结束,避免挂起占用连接。
- 所有用到Mongo事务、游标、Change Stream的代码,必须在正常分支和异常分支都主动调用
close()方法释放资源,不要等垃圾回收自动清理。 - 把EB前置Nginx的
proxy_read_timeout调整为15秒,比Mongoose的socket超时多留5秒缓冲,确保DB超时后Node能正常返回错误响应,不会出现Nginx已断开、Node还在挂起占连接的情况。 - 可以先配置EB实例定期自动重启(比如每24小时滚动重启一次)作为临时兜底措施,但最终必须定位并修复连接泄漏的代码点,否则问题还是会随机复发。
内容的提问来源于stack exchange,提问作者Omar Zahir
相关产品推荐
相关产品推荐

