为何我的AWS Lambda函数会创建两个Spot Instance Requests而非一个?
问题描述
我编写了如下Lambda函数,本应根据传入参数创建一个Spot实例请求,但实际却总是并行创建两个各含一个实例的Spot请求。CloudWatch显示函数仅触发一次,且成功率为100%,日志中无报错信息,我已将超时时间设置为20分钟,排除了超时导致的重复执行可能。请问为何会出现这种情况?我仅需要创建一个请求而非两个,恳请提供帮助。
我的Lambda代码:
var AWS = require('aws-sdk'); var ec2 = new AWS.EC2({ region: "eu-west-1" }); var userData = `#!/bin/bash echo "hello there" `; var userDataEncoded = new Buffer.from(userData).toString('base64'); var params = { InstanceCount: 1, LaunchSpecification: { ImageId: "ami-xxxxxxxxx", InstanceType: "c4.2xlarge", KeyName: "xxxxxxx", SubnetId: "subnet-xxxxxxxxxx", Placement: { AvailabilityZone: "eu-west-1a" }, SecurityGroupIds: [ "sg-xxxxxxxxxx" ], UserData: userDataEncoded }, SpotPrice: "0.8", BlockDurationMinutes: 180, Type: "one-time" }; exports.handler = async (event, context) => { await ec2.requestSpotInstances(params, function (err, data) { if (err) { console.log("error"); } else { console.log("starting instance"); context.succeed('Completed'); return { statusCode: 200, body: JSON.stringify('success!'), }; } }).promise(); };
解决方案
问题的核心是你同时混用了AWS SDK的回调函数模式和Promise/async-await模式,这直接导致了两次独立的Spot实例请求被发起。
具体原因分析
AWS SDK的方法支持两种调用方式:
- 回调模式:传入回调函数作为参数,方法会立即发起请求,完成后调用回调。
- Promise模式:调用
.promise()方法,返回一个Promise对象,可配合await使用。
你的代码中,既给ec2.requestSpotInstances传入了回调函数,又链式调用了.promise()——这会触发两次API调用:一次是回调模式的请求,一次是Promise模式的请求。这就是为什么你会看到两个Spot实例请求被创建。
另外,在Lambda的async handler中,调用context.succeed是多余的(甚至会导致执行流程混乱),因为async函数的返回值会自动作为Lambda的响应,无需手动调用上下文方法。
修正后的代码
下面是修复后的代码,只使用Promise/async-await模式,确保只发起一次请求:
var AWS = require('aws-sdk'); var ec2 = new AWS.EC2({ region: "eu-west-1" }); // 修复userData的格式:#!/bin/bash需要单独一行,否则脚本无法正确执行 var userData = `#!/bin/bash echo "hello there"`; var userDataEncoded = new Buffer.from(userData).toString('base64'); var params = { InstanceCount: 1, LaunchSpecification: { ImageId: "ami-xxxxxxxxx", InstanceType: "c4.2xlarge", KeyName: "xxxxxxx", SubnetId: "subnet-xxxxxxxxxx", Placement: { AvailabilityZone: "eu-west-1a" }, SecurityGroupIds: [ "sg-xxxxxxxxxx" ], UserData: userDataEncoded }, SpotPrice: "0.8", BlockDurationMinutes: 180, Type: "one-time" }; exports.handler = async (event, context) => { try { // 仅使用Promise模式,移除回调函数 const data = await ec2.requestSpotInstances(params).promise(); console.log("Starting spot instance request:", data); return { statusCode: 200, body: JSON.stringify('Success!'), }; } catch (err) { console.error("Error requesting spot instance:", err); return { statusCode: 500, body: JSON.stringify('Failed to create spot instance request'), }; } };
额外优化点
- 修复了userData格式:原代码中
#!/bin/bash和echo命令在同一行,这会导致脚本执行失败,修正后将shebang单独放在第一行。 - 使用try/catch处理错误:替代回调模式的错误处理,更符合async函数的编码习惯,也能更清晰地捕获和记录错误。
- 移除了多余的context.succeed:Lambda的async handler会自动处理函数的完成状态,返回响应对象即可。
内容的提问来源于stack exchange,提问作者Jan v
相关产品推荐
相关产品推荐

