AWS Lambda Node.js函数重复调用出现Promise拒绝及运行时退出错误排查
问题排查:AWS Lambda快速连续调用时的异常问题
现象描述
- 首次调用Lambda函数成功,返回结果正常
- 再次快速调用时,返回
Error: Runtime exited with error: exit status 128和ERROR Unhandled Promise Rejection Runtime.UnhandledPromiseRejection - 失败调用后再次调用,函数又能正常运行并返回正确结果
- 已设置
context.callbackWaitsForEmptyEventLoop = false,但问题依旧
排查方向及修复建议
1. 数据库连接缓存的潜在问题
代码中仅缓存了MongoDB的db实例,未缓存client实例。当Lambda复用执行环境时,若client连接失效或被销毁,缓存的db实例会处于不可用状态,导致后续调用失败。
修复方案:同时缓存client和db,并添加连接有效性检查:
const MongoClient = require("mongodb").MongoClient; const ObjectId = require("mongodb").ObjectId; const MONGODB_URI = process.env.MONGODB_URI; let cachedClient = null; let cachedDb = null; async function connectToDatabase() { if (cachedDb && cachedClient && cachedClient.isConnected()) { return cachedDb; } const client = await MongoClient.connect(MONGODB_URI, { maxPoolSize: 10, connectTimeoutMS: 30000, socketTimeoutMS: 30000 }); const db = await client.db(process.env.MONGODB_NAME); cachedClient = client; cachedDb = db; return db; }
2. 未处理的Promise拒绝
代码在catch块中仅打印错误,未返回错误响应。当Lambda函数抛出未处理的Promise拒绝时,会触发运行时异常,进而导致exit status 128进程终止错误。
修复方案:在catch块中返回标准错误响应,确保所有执行路径都有返回值:
try { // ... 原有业务逻辑 } catch (error) { console.error(error); return { statusCode: 500, headers: { "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Origin": "http://localhost:3000/", "Access-Control-Allow-Methods": "*" }, body: JSON.stringify({ error: error.message }) }; }
3. 输入参数校验缺失
快速连续调用时,可能存在参数未定义或格式错误的情况(比如event.queryStringParameters为undefined、event.body为空),导致JSON.parse(event.body)抛出错误,触发未处理的Promise拒绝。
修复方案:添加参数校验逻辑,避免非法输入导致崩溃:
exports.handler = async (event, context) => { console.log(event.queryStringParameters); console.log(event.body); // 校验查询参数是否存在 if (!event.queryStringParameters) { return { statusCode: 400, headers: { "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Origin": "http://localhost:3000/", "Access-Control-Allow-Methods": "*" }, body: JSON.stringify({ error: "缺少查询参数" }) }; } const { docType, docId, fundId } = event.queryStringParameters; // 校验必要参数 if (!docType || !docId || !fundId) { return { statusCode: 400, headers: { "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Origin": "http://localhost:3000/", "Access-Control-Allow-Methods": "*" }, body: JSON.stringify({ error: "缺少必要参数:docType、docId、fundId" }) }; } // 校验请求体格式 let bodyData; try { bodyData = JSON.parse(event.body || "{}"); } catch (parseError) { return { statusCode: 400, headers: { "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Origin": "http://localhost:3000/", "Access-Control-Allow-Methods": "*" }, body: JSON.stringify({ error: "请求体格式错误" }) }; } // ... 原有业务逻辑 };
4. 并发更新冲突处理
快速连续调用时,可能出现同一文档被并发更新的情况,导致MongoDB抛出异常。虽然使用了findOneAndUpdate,但未处理冲突会引发错误。
修复方案:添加重试逻辑处理并发冲突:
async function updateDocument(db, docType, docId, fundId, bodyData) { let retryCount = 0; const maxRetries = 3; while (retryCount < maxRetries) { try { return await db.collection(docType).findOneAndUpdate( { _id: ObjectId(docId), "profile_subscription_documents.fund_id": fundId }, { $push: { "profile_subscription_documents.$[psd].profile_fund_subscription_documents": { ...bodyData } } }, { arrayFilters: [{ "psd.fund_id": fundId }], returnDocument: "after" } ); } catch (updateError) { retryCount++; if (retryCount >= maxRetries) throw updateError; await new Promise(resolve => setTimeout(resolve, 100)); } } } // 在handler中调用 const data = await updateDocument(db, docType, docId, fundId, bodyData);
总结
优先排查未处理的Promise拒绝和数据库连接缓存有效性这两个核心问题,它们是导致exit status 128和调用失败的常见原因。同时完善参数校验和错误处理逻辑,可大幅提升函数稳定性。
内容的提问来源于stack exchange,提问作者Rich
相关产品推荐
相关产品推荐

