多次数据库请求后Heroku应用崩溃(H10错误)问题排查与解决
解决Heroku上React+Node(TypeScript)应用连接ClearDB MySQL崩溃的问题
问题回顾
你在Heroku上部署React+Node(TypeScript)应用,搭配免费版ClearDB MySQL附加组件时遇到了这类问题:
- 数据库请求次数较少时应用运行完全正常
- 当请求达到约10次(服务器未休眠状态)后,后续请求会直接导致应用崩溃
- 页面刷新后无法正常加载,仅显示“Application Error”,错误日志中能观察到H10错误
进一步排查后确认核心问题是数据库连接丢失,Heroku会在连续出现多个错误时自动关闭应用,对应的错误日志如下:
2021-08-30T11:25:45.178960+00:00 app[web.1]: > api@1.0.0 start 2021-08-30T11:25:45.178960+00:00 app[web.1]: > ts-node server.ts 2021-08-30T11:25:45.178961+00:00 app[web.1]: 2021-08-30T11:25:49.764096+00:00 heroku[web.1]: State changed from starting to up 2021-08-30T11:25:49.701827+00:00 app[web.1]: APP running on port 26858 2021-08-30T11:26:01.705662+00:00 heroku[router]: at=info method=GET path="/api/message" host=random-messenger.herokuapp.com request_id=1fac5c60-c66c-425a-acb4-17f173b72870 fwd="188.167.229.146" dyno=web.1 connect=0ms service=81ms status=200 bytes=309 protocol=https 2021-08-30T11:26:02.705066+00:00 heroku[router]: at=info method=GET path="/api/message" host=random-messenger.herokuapp.com request_id=56879ea0-382d-42ca-b757-ed1d6e4d5137 fwd="188.167.229.146" dyno=web.1 connect=0ms service=9ms status=200 bytes=361 protocol=https 2021-08-30T11:26:03.599888+00:00 heroku[router]: at=info method=GET path="/api/message" host=random-messenger.herokuapp.com request_id=8f8fa4ef-0535-4cdd-abbd-6f93eeae0941 fwd="188.167.229.146" dyno=web.1 connect=0ms service=11ms status=200 bytes=312 protocol=https 2021-08-30T11:27:01.162648+00:00 app[web.1]: /app/api/node_modules/mysql/lib/protocol/Protocol.js:112 2021-08-30T11:27:01.162660+00:00 app[web.1]: var err = new Error('Connection lost: The server closed the connection.'); 2021-08-30T11:27:01.162661+00:00 app[web.1]: ^ 2021-08-30T11:27:01.164283+00:00 app[web.1]: Error: Connection lost: The server closed the connection. 2021-08-30T11:27:01.164284+00:00 app[web.1]: at Protocol.end (/app/api/node_modules/mysql/lib/protocol/Protocol.js:112:13) 2021-08-30T11:27:01.164284+00:00 app[web.1]: at Socket.<anonymous> (/app/api/node_modules/mysql/lib/Connection.js:94:28) 2021-08-30T11:27:01.164285+00:00 app[web.1]: at Socket.<anonymous> (/app/api/node_modules/mysql/lib/Connection.js:526:10) 2021-08-30T11:27:01.164286+00:00 app[web.1]: at Socket.emit (node:events:406:35) 2021-08-30T11:27:01.164286+00:00 app[web.1]: at Socket.emit (node:domain:475:12) 2021-08-30T11:27:01.164286+00:00 app[web.1]: at endReadableNT (node:internal/streams/readable:1331:12) 2021-08-30T11:27:01.164286+00:00 app[web.1]: at processTicksAndRejections (node:internal/process/task_queues:83:21) { 2021-08-30T11:27:01.164287+00:00 app[web.1]: fatal: true, 2021-08-30T11:27:01.164287+00:00 app[web.1]: code: 'PROTOCOL_CONNECTION_LOST' 2021-08-30T11:27:01.164287+00:00 app[web.1]: } 2021-08-30T11:27:01.316331+00:00 heroku[web.1]: State changed from up to crashed 2021-08-30T11:27:01.228791+00:00 heroku[web.1]: Process exited with status 1 2021-08-30T11:28:17.001351+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/receive" host=random-messenger.herokuapp.com request_id=a5d2639a-b804-4706-bc75-ed236fbbaa25 fwd="188.167.229.146" dyno= connect= service= status=503 bytes= protocol=https 2021-08-30T11:28:17.461640+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=random-messenger.herokuapp.com request_id=78e7a216-d0c3-4c7f-928b-b10ae7dff3ae fwd="188.167.229.146" dyno= connect= service= status=503 bytes= protocol=https
问题根源
免费版ClearDB MySQL附加组件有个容易被忽略的限制:仅允许同时存在10个数据库连接。你的应用当前的实现是每次请求都新建一个数据库连接,没有做连接复用,当请求次数达到10次后,连接配额被完全耗尽,新的请求无法获取可用连接,最终触发连接丢失错误,导致Heroku强制关闭应用,出现H10报错。
解决方案
修改你的数据库连接逻辑,改为复用连接(使用连接池),而不是每次请求都创建新连接:
- 如果你使用的是
mysql包,用createPool方法创建连接池,替代createConnection - 请求处理完成后,不要直接关闭连接,而是让连接自动归还到池中
- 配置合理的连接池参数,比如
connectionLimit设置为8-9,给ClearDB留少量余量避免触发限制
这里给一个简单的TypeScript示例:
import mysql from 'mysql'; // 创建数据库连接池 const pool = mysql.createPool({ host: process.env.CLEARDB_HOST, user: process.env.CLEARDB_USER, password: process.env.CLEARDB_PASSWORD, database: process.env.CLEARDB_DATABASE, connectionLimit: 8, // 不超过ClearDB免费版的10连接限制 }); // 在接口中使用连接池处理请求 app.get('/api/message', (req, res) => { pool.query('SELECT * FROM messages', (error, results) => { if (error) throw error; res.json(results); }); });
修改后,连接会被循环复用,不会快速耗尽ClearDB的连接配额,应用就能稳定运行了。
内容的提问来源于stack exchange,提问作者Matej Peluha
相关产品推荐
相关产品推荐

