如何在AWS Lambda中对非幂等API发起并发/并行请求?
解决方案:批量并发处理生日用户礼品卡任务
Node.js 并发实现方案
可以通过控制并发批次的方式,在单个Lambda内高效处理大量用户,避免超时问题。推荐使用p-limit库限制并发数,平衡数据库和邮件服务的压力。
核心代码示例
const pLimit = require('p-limit'); // 控制并发数,根据数据库和邮件服务的承载能力调整(比如20) const concurrencyLimit = pLimit(20); // Lambda 入口函数 exports.handler = async (event) => { let hasMoreUsers = true; const currentYear = new Date().getFullYear().toString(); const currentMonth = (new Date().getMonth() + 1).toString().padStart(2, '0'); while (hasMoreUsers) { // 1. 查询获取100位待处理的当月生日用户 const users = await fetchEligibleUsers(currentYear, currentMonth); if (users.length === 0) { hasMoreUsers = false; break; } // 2. 并发处理用户任务 const processingPromises = users.map(user => concurrencyLimit(async () => { try { // 为用户创建礼品卡并写入数据库 await createAndSaveGiftCard(user, currentYear, currentMonth); // 发送生日礼品邮件 await sendBirthdayGiftEmail(user); } catch (error) { console.error(`处理用户ID ${user.id} 失败:`, error); // 可选:将失败用户信息存入SQS队列,后续触发重试 } })); // 等待当前批次所有任务完成 await Promise.all(processingPromises); } return { statusCode: 200, body: '所有生日用户礼品卡任务处理完成' }; }; // 数据库查询:获取符合条件的用户 async function fetchEligibleUsers(year, month) { const sql = ` SELECT u.id, u.first_name, u.last_name, u.email, u.date_of_birth FROM user u LEFT JOIN gift_card gc ON gc.scheduled_at >= ? AND gc.scheduled_at < ? AND gc.message = 'Happy birthday! from BURST' AND gc.status = 1 AND gc.recipient_email = u.email WHERE MONTH(u.date_of_birth) = ? AND u.email IS NOT NULL AND u.status = 10 AND gc.recipient_email IS NULL LIMIT 100 `; // 替换为你的数据库客户端执行逻辑(如mysql2、pg等) // const [rows] = await dbConnection.execute(sql, [`${year}-01-01`, `${parseInt(year)+1}-01-01`, month]); // return rows; return []; // 示例返回空数组,实际替换为查询结果 } // 创建并保存礼品卡到数据库 async function createAndSaveGiftCard(user, year, month) { // 实现礼品卡插入逻辑,生成礼品码等 const giftCardSql = ` INSERT INTO gift_card (recipient_email, message, status, scheduled_at, gift_code) VALUES (?, 'Happy birthday! from BURST', 1, ?, ?) `; const giftCode = generateUniqueGiftCode(); // 实现礼品码生成逻辑 // await dbConnection.execute(giftCardSql, [user.email, `${year}-${month}-01`, giftCode]); } // 发送生日礼品邮件 async function sendBirthdayGiftEmail(user) { // 实现邮件发送逻辑(如使用AWS SES) // await sesClient.sendEmail({ // Destination: { ToAddresses: [user.email] }, // Message: { /* 邮件内容,包含礼品码 */ }, // Source: 'your-sender-email@example.com' // }); } // 生成唯一礼品码 function generateUniqueGiftCode() { return Math.random().toString(36).substring(2, 10).toUpperCase(); }
关键注意事项
- 并发数需根据数据库连接池大小、邮件服务发送限制调整,避免触发限流。
- 失败任务建议存入SQS队列,配置单独的Lambda进行重试,避免丢失任务。
- Lambda需配置足够的内存(如512MB)和超时时间(最大15分钟),确保单批次处理完成。
AWS 服务编排方案
如果用户量极大(十万级以上),单Lambda处理可能仍有瓶颈,推荐使用AWS原生服务组合实现分布式并发处理:
方案1:SQS + Lambda 分布式处理
- 触发Lambda:每月1号通过CloudWatch Events触发第一个Lambda,批量查询所有符合条件的用户,将每个用户的信息以消息形式发送到SQS标准队列。
- 消费Lambda:配置SQS触发第二个Lambda,每个Lambda实例处理1-5条用户消息(可在SQS触发器设置批量大小),自动并发扩容,处理礼品卡创建和邮件发送。
- 重试机制:SQS自动处理失败消息(可设置重试次数和延迟),最终将无法处理的消息存入死信队列,人工排查。
方案2:Step Functions 工作流编排
- 触发工作流:每月1号通过CloudWatch Events触发Step Functions状态机。
- 获取用户列表:调用Lambda查询100位用户。
- 并行处理:使用Step Functions的
Map状态,并行处理每个用户的礼品卡创建和邮件发送任务(可设置并发数上限)。 - 循环迭代:处理完当前批次后,判断是否还有未处理用户,循环执行查询和处理步骤,直到所有用户处理完成。
SQL 查询优化建议
原SQL使用DATE_FORMAT函数会导致索引失效,降低查询效率,建议修改为范围查询和函数优化:
SELECT u.id, u.first_name, u.last_name, u.email, u.date_of_birth FROM user u LEFT JOIN gift_card gc ON -- 用范围查询替代DATE_FORMAT,利用scheduled_at索引 gc.scheduled_at >= CONCAT(?,'-01-01') AND gc.scheduled_at < CONCAT(CAST(? AS UNSIGNED)+1,'-01-01') AND gc.message = 'Happy birthday! from BURST' AND gc.status = 1 AND gc.recipient_email = u.email WHERE -- 直接提取月份,若user表有date_of_birth索引,可考虑新增计算字段birth_month MONTH(u.date_of_birth) = ? AND u.email IS NOT NULL AND u.status = 10 AND gc.recipient_email IS NULL LIMIT 100
- 建议为
user.date_of_birth添加索引,或新增计算字段birth_month(存储1-12)并建立索引。 - 为
gift_card.scheduled_at和gift_card.recipient_email建立联合索引,加速关联查询。
内容的提问来源于stack exchange,提问作者Piyush Keshari
相关产品推荐
相关产品推荐

