You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AWS Lambda(Node.js)搭配RDS Proxy时的数据库连接异常问询

问题:RDS Proxy连接频繁创建销毁+AbortedClients数值过高排查

已将AWS Lambda(Node.js)的数据库端点切换为RDS Proxy,期望通过RDS Proxy管理连接池,让数据库仅显示连接池的稳定连接。但实际执行SHOW FULL PROCESSLIST时,发现来自RDS Proxy IP的数据库连接会频繁创建后立即断开,不同时间点的结果如下:

14:28:07

IdUserHostdbCommandTime
113246testadmin10.0.3.102:23953testmainSleep0
113247testadmin10.0.3.102:33423testmainSleep0

14:28:12

IdUserHostdbCommandTime
113429testadmin10.0.3.102:25645testmainSleep0
113430testadmin10.0.3.102:52747testmainSleep0

14:28:15

IdUserHostdbCommandTime
113496testadmin10.0.3.102:45863testmainSleep0
113497testadmin10.0.3.102:62891testmainSleep0
113500testadmin10.0.3.102:24823testmainSleep0
113501testadmin10.0.3.102:18577testmainSleep0

同时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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:44:50