解决Heroku上基础数据检索应用的H12超时错误
关于15秒超时Ping中间件的可行性
- 给客户端发ping(比如HTTP分块响应)理论上可行,但无法解决Heroku H12错误。Heroku的30秒超时从请求发起时开始计算,即便发送中间ping,只要整体处理超30秒仍会触发H12,且会持续占用客户端连接资源。
- 给服务器内部发ping(比如监控进程状态)可用于记录慢请求,但对避免超时崩溃帮助有限,更多是排查问题的手段。
核心缓解方案(针对间歇性超时+重启后恢复的场景)
1. 优化MongoDB查询性能
- 检查超时请求的查询条件:比如
/index接口的customerType、town、zipcode过滤逻辑,确认是否缺少复合索引。用db.collection.explain("executionStats")分析执行计划,排查是否存在全表扫描。 - 转移计算压力到数据库:让MongoDB完成排序(基于索引)、分页(
skip()+limit()),避免拉取大量数据到Node.js内存中处理,减少内存占用和耗时。 - 修复连接池异常:间歇性超时大概率是MongoDB连接池耗尽或状态异常。调整
maxPoolSize等连接池配置,添加连接重连逻辑,避免请求堆积等待连接。
2. 应用层超时控制
- 添加请求超时中间件,主动终止慢请求并释放资源,示例代码(用
express-timeout-handler):
const timeoutHandler = require('express-timeout-handler'); app.use(timeoutHandler.handler({ timeout: 25000, // 设置25秒超时,预留缓冲时间 onTimeout: (req, res) => { res.status(503).send('请求超时,请稍后重试'); // 可在此添加慢请求日志记录 } }));
- 给MongoDB查询单独设置超时:在查询选项中添加
maxTimeMS,比如collection.find(query).maxTimeMS(20000),防止单个查询卡住整个请求。
3. 避免应用崩溃的保障措施
- 捕获全局异常,防止单个请求错误导致dyno崩溃:
process.on('uncaughtException', (err) => { console.error('未捕获异常:', err); // 可选:记录日志后优雅重启进程 }); process.on('unhandledRejection', (reason, promise) => { console.error('未处理Promise拒绝:', reason); });
- 确保Heroku自动重启生效:正确配置
Procfile,让Heroku在进程崩溃时自动重启,但核心还是要解决超时根源问题。
4. 监控与排查
- 添加请求耗时日志,定位慢请求:
app.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; console.log(`请求 ${req.method} ${req.path} 耗时 ${duration}ms`); if (duration > 15000) { console.warn('慢请求:', req.path, req.query); } }); next(); });
- 查看Heroku的dyno指标,确认超时发生时的CPU、内存使用率,排查是否存在资源耗尽情况(Hobby Dyno资源有限,高负载易触发超时)。
错误日志整理
2022-08-23T14:05:25.673220+00:00 heroku[router]: at=error code=H12 desc="Request timeout" method=GET path="/index?customerType=Buyer&town=&zipcode=01235" host=www.agentometer.com request_id=6350ff01-69ed-4c4b-b958-d880b5ceef73 fwd="98.216.185.71" dyno=web.1 connect=0ms service=30000ms status=503 bytes=0 protocol=https
2022-08-23T14:06:34.759586+00:00 heroku[router]: at=error code=H12 desc="Request timeout" method=GET path="/index?customerType=Buyer&town=Boston&zipcode=" host=www.agentometer.com request_id=da8df716-44c9-4c5b-8db6-96d11d299302 fwd="98.216.185.71" dyno=web.1 connect=0ms service=30000ms status=503 bytes=0 protocol=https
2022-08-23T14:26:10.643969+00:00 heroku[router]: at=error code=H12 desc="Request timeout" method=GET path="/" host=www.agentometer.com request_id=c7a9472d-5a7a-4250-83f7-827b4416019e fwd="108.26.205.219" dyno=web.1 connect=0ms service=30000ms status=503 bytes=0 protocol=https
2022-08-23T14:26:12.036207+00:00 heroku[router]: at=error code=H12 desc="Request timeout" method=GET path="/" host=www.agentometer.com request_id=13978436-5a55-4c41-ad60-2416fd0e853c fwd="108.26.205.219" dyno=web.1 connect=0ms service=30000ms status=503 bytes=0 protocol=https
内容的提问来源于stack exchange,提问作者CBZ2022

