使用AWS Lambda(Node.js)搭配RDS Proxy时的数据库连接异常问询
已将AWS Lambda(Node.js)的数据库端点切换为RDS Proxy,期望通过RDS Proxy管理连接池,让数据库仅显示连接池的稳定连接。但实际执行SHOW FULL PROCESSLIST时,发现来自RDS Proxy IP的数据库连接会频繁创建后立即断开,不同时间点的结果如下:
14:28:07
Id User Host db Command Time 113246 testadmin 10.0.3.102:23953 testmain Sleep 0 113247 testadmin 10.0.3.102:33423 testmain Sleep 0 14:28:12
Id User Host db Command Time 113429 testadmin 10.0.3.102:25645 testmain Sleep 0 113430 testadmin 10.0.3.102:52747 testmain Sleep 0 14:28:15
Id User Host db Command Time 113496 testadmin 10.0.3.102:45863 testmain Sleep 0 113497 testadmin 10.0.3.102:62891 testmain Sleep 0 113500 testadmin 10.0.3.102:24823 testmain Sleep 0 113501 testadmin 10.0.3.102:18577 testmain Sleep 0
同时AWS Cloudwatch中AbortedClients数值很高,需确认这种连接频繁创建销毁是否正常,以及AbortedClients过高的原因。
技术环境
- Node.js 14.x
- RDS实例:db.t4g.medium,MySQL版本8.0.mysql_aurora.3.05.2
- Lambda相关代码:
mysql_proxy_connection.js
const mysql = require("mysql2/promise"); let mysql_options; module.exports = { init: async () => { if(mysql_options) return; ... (加载mysql_options参数) mysql_options = { host: process.env.DB_PROXY_HOST, port: process.env.DB_PORT, user: process.env.DB_USER, password: password, database: process.env.DB_NAME, connectTimeout: 5000 }; }, // 获取连接 getConnection: async () => { let mysql_conn; try { mysql_conn = await mysql.createConnection(mysql_options); return mysql_conn; } catch (error) { if (mysql_conn) { await mysql_conn.end(); } throw error; } } };
function.js
"use strict"; const mysql = require("../modules/mysql_proxy_connection"); const lambda = require("../modules/invokeLambda"); module.exports.functionLambda = async (event) => { // UTC + 9小时 const timezone = "9h"; let response = {}; let mySqlConn; let date = new Date(); date.setHours(date.getHours() + 9); let baseDate = date.toISOString().substring(0, 10).replace(/-/g, ""); try{ let topic = event['topic']; let deviceNo = topic.split('/')[2]; if (event['body']['report_type'] === "event" && event['body']?.['event_type'] === "bootup") { await mysql.init(); mySqlConn = await mysql.getConnection(); let splits = deviceNo.split('-'); let deviceNumber = splits[1]; let vms_queryString = ` SELECT * FROM device WHERE device_name = '${deviceNumber}'`; let vms_result = await mysql.execQuery(mySqlConn, vms_queryString); if(vms_result === undefined || vms_result.length === 0) { let errMsg = `${deviceNo} 发送了BOOTUP事件信号,但VMS中无该设备信息`; mySqlConn.end(); throw Error(errMsg); } let fw_version = event['body']?.['boot']?.['fw_version']; if ( vms_result[0]['firmware_version'] !== fw_version) { let vms_fw_update_queryString = `UPDATE device SET firmware_version = '${fw_version}' WHERE (device_name = '${deviceNumber}')`; await update_firmware(mySqlConn, vms_fw_update_queryString); } } await lambda.invokeLambda(process.env.LAMBDA_CONVERT_DRIVING, JSON.stringify(lambda_invoke_event)); response = { statusCode: 200, baseDate }; if(mySqlConn) { mySqlConn.end(); } return response; } catch (err) { response = { statusCode: 500, body: err.message }; if(mySqlConn) { mySqlConn.end(); } return response; } } const update_firmware = async (mySqlConn, vms_fw_update_queryString) => { try { await mySqlConn.beginTransaction(); await mysql.execQuery(mySqlConn, vms_fw_update_queryString); await mySqlConn.commit(); } catch (err) { await mySqlConn.rollback(); throw err; } }
handler.js
'use strict'; const func = require("./function"); module.exports.handleFunctionLambda = async (event, context, callback) => { context.callbackWaitsForEmptyEventLoop = false; try { await func.functionLambda(event); return callback(null, { statusCode: 200 }) } catch (err) { return callback(null, { statusCode: 400, body: err }) } }
1. 连接频繁创建销毁是否正常?
这种情况不正常。RDS Proxy的核心作用是复用数据库连接,避免频繁创建销毁带来的性能损耗。出现该现象说明RDS Proxy未正确复用连接,而是每次都向RDS实例新建连接。
2. 问题根源分析
(1)Lambda代码每次请求都创建并主动关闭连接
代码中每次Lambda触发时都会调用mysql.createConnection()创建新客户端连接到RDS Proxy,且请求结束后调用mySqlConn.end()主动关闭连接。
尽管RDS Proxy会维护后端RDS连接池,但如果客户端(Lambda)主动断开连接,RDS Proxy会判定该客户端连接不再需要,进而关闭对应后端RDS连接(或根据闲置超时设置销毁),最终导致频繁创建销毁现象。
(2)AbortedClients过高的原因
AbortedClients统计的是客户端主动断开连接的次数。代码中每次请求结束都主动关闭连接,会被MySQL计入该指标;此外RDS Proxy因客户端连接断开而关闭后端RDS连接,也会被统计在内。
另外,代码存在SQL注入风险(直接拼接deviceNumber到SQL语句),虽与当前问题无关,但需及时修复。
3. 修复方案
(1)改用连接池模式,复用连接
修改mysql_proxy_connection.js为连接池实现,利用RDS Proxy的连接复用特性:
const mysql = require("mysql2/promise"); let pool; module.exports = { init: async () => { if(pool) return; ... (加载参数) pool = mysql.createPool({ host: process.env.DB_PROXY_HOST, port: process.env.DB_PORT, user: process.env.DB_USER, password: password, database: process.env.DB_NAME, connectTimeout: 5000, // 适配RDS Proxy的连接池参数 waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); }, getConnection: async () => { try { return await pool.getConnection(); } catch (error) { throw error; } }, // 释放连接回池,而非直接关闭 releaseConnection: async (conn) => { if(conn) conn.release(); } };
(2)修改Lambda代码,释放连接而非关闭
在function.js中,将所有mySqlConn.end()替换为mysql.releaseConnection(mySqlConn):
// 原代码:mySqlConn.end(); // 修改为: mysql.releaseConnection(mySqlConn);
(3)保留Lambda执行环境复用配置
handler.js中context.callbackWaitsForEmptyEventLoop = false的配置正确,该设置会让Lambda执行完后不等待事件循环为空,从而保留执行环境中的连接池。
(4)修复SQL注入问题
使用参数化查询替代直接拼接SQL:
// 原代码 let vms_queryString = ` SELECT * FROM device WHERE device_name = '${deviceNumber}'`; let vms_result = await mysql.execQuery(mySqlConn, vms_queryString); // 修改为参数化查询 let vms_result = await mySqlConn.execute('SELECT * FROM device WHERE device_name = ?', [deviceNumber]);
4. RDS Proxy配置检查
- 确认RDS Proxy的连接闲置超时时间(默认30分钟)是否合理,避免设置过短导致连接过早销毁。
- 确认RDS Proxy的最大连接数是否适配RDS实例的连接限制(db.t4g.medium的MySQL默认连接数可通过参数组调整)。
内容的提问来源于stack exchange,提问作者Gi Yeon Shin

