如何解决我的 lambda 函数的一部分执行两次
我正在编写 AWS lambda 函数,该函数应该在玩家每次进行训练时更新他的每日统计数据。函数在新 DynamoDB 行事件上触发一次,配置了 0 次重试。
我正在单独测试该函数,我确定我只在 DynamoDB 中创建了一行来触发 lambda 执行。现在到了有趣的部分。我的 lambda 作为一个整体执行一次,但它的某些部分在同一个请求中执行两次。
这是 lambda 代码:
const AWS = require('aws-sdk');
var docclient = new AWS.DynamoDB.DocumentClient({region: 'us-west-2'});
const dailyStatsTableName = 'xxx';
const weeklyStatsTableName = 'yyy';
const monthlyStatsPlayerTableName = 'zzz';
const requestsTableName = 'qqq';
exports.handler = async (event,context,callback) => {
//Ensure idempotency of lambda as a whole
let requestID = context.awsRequestId;
let requestAlreadyProcessed = false;
await getRequest(requestsTableName,requestID,(err,data) => {
if (err) {
console.log(err);
} else {
if (data.Item) {
requestAlreadyProcessed = true;
}
}
});
if (requestAlreadyProcessed) {
console.log("This request has already been processed. Aborting.");
return;
}
console.log("Processing new assigned drill performance event. RequestID: " + requestID);
console.log(event);
const record = event.Records[0].dynamodb;
console.log(record);
if (!record || !record.NewImage) {
console.log("New record image undefined");
return;
}
console.log(record.NewImage);
//Get performed touches to count them in statistics
let touches = 0;
try {
touches = parseInt(record.NewImage.touches.N,10);
} catch (error) {
console.error(error);
}
//Unpack date from record.NewImage.createdAt string
let performanceDate = new Date(Date.parse(record.NewImage.createdAt.S));
console.log("CreatedAt date: " + performanceDate);
//Get daily statistics object from table - I want to update if already exist
let dailyStats = {
drillID: record.NewImage.drillID.S,targetUserID: record.NewImage.targetUserID.S,performDay: performanceDate.getDate(),performMonth: performanceDate.getMonth(),performYear: performanceDate.getFullYear(),performDate: performanceDate.toISOString(),touches: 0,id: undefined
};
let result = await getDailyStats(dailyStatsTableName,dailyStats.performDay,dailyStats.performMonth,dailyStats.performYear,dailyStats.drillID,dailyStats.targetUserID,data) => {
if (err) {
console.log(err);
} else {
if (data.Items.length !== 0) {
console.log("Found daily stats object"); //this console.log is logged twice. Everything below that line is executed twice.
dailyStats = data.Items[0];
}
}
return "success";
});
//Create or update daily statistics
if (!dailyStats.id) {
console.log("DailyStats ID not found. Creating new with touches " + touches);
dailyStats.touches = touches;
result = await createDailyStats(dailyStatsTableName,dailyStats,data) => {
if (err) {
console.log(err);
} else {
console.log("Success creating daily stats " + dailyStats.drillID + " " + dailyStats.targetUserID + " " + dailyStats.touches);
}
return "success";
});
} else {
console.log("DailyStats ID found. Updating existing with touches " + touches);
result = await updateDailyStats(dailyStatsTableName,dailyStats.id,touches,data) => {
if (err) {
console.log(err);
} else {
console.log("Success updating daily stats " + dailyStats.drillID + " " + dailyStats.targetUserID + " " + touches);
}
return "success";
});
}
//Mark this request as processed to ensure idempotency of lambda as a whole
result = await createProcessedRequest(requestsTableName,data) => {
if (err) {
console.log(err);
} else {
console.log("Success creating processed request " + requestID);
}
return "success";
});
return "success";
};
function createDailyStats(tableName,stats,callback) {
let Now = new Date();
let dateTimeString = Now.toISOString();
let params = {
TableName:tableName,Item:{
"__typename": "PersonalDrillDailyStatistic","createdAt": dateTimeString,"updatedAt": dateTimeString,"id": stats.id ? stats.id : createUUID(),"drillID": stats.drillID,"targetUserID": stats.targetUserID,"touches": stats.touches,"performDay": stats.performDay,"performMonth": stats.performMonth,"performYear": stats.performYear,"lastRequestID": requestID
}
};
console.log("Adding a new daily stats (with id) item... " + stats.drillID + " " + stats.targetUserID + " " + stats.touches);
return docclient.put(params,callback).promise();
}
function updateDailyStats(tableName,statsID,callback) {
var params = {
TableName:tableName,Key:{
"id": statsID
},UpdateExpression: "set touches = touches + :val,lastRequestID = :reqID",ExpressionAttributeValues:{
":val": touches,":reqID": requestID
},ConditionExpression: "lastRequestID <> :reqID",//conditional check exception is being thrown during second call,hence Exception in logs output
ReturnValues:"UPDATED_NEW"
};
console.log("Updating daily stats (with id) item... " + statsID + " " + touches);
return docclient.update(params,callback).promise();
}
function getDailyStats(tableName,performDay,performMonth,performYear,drillID,targetUserID,callback) {
console.log("Querying for daily statistics |" + performDay + "." + performMonth + "." + performYear + "| userID: |" + targetUserID + "| drillID: |" + drillID + "| from table " + tableName);
let params = {
TableName: tableName,FilterExpression: "drillID = :drill_id and targetUserID = :user_id and performDay = :day and performMonth = :month and performYear = :year",ExpressionAttributeValues: {
":drill_id": drillID,":user_id": targetUserID,":day": performDay,":month": performMonth,":year": performYear,}
};
return docclient.scan(params,callback).promise();
}
function createUUID(){
(...)
}
function getRequest(tableName,callback) {
let params = {
TableName: tableName,Key: {
"id": requestID
}
};
return docclient.get(params,callback).promise();
}
function createProcessedRequest(tableName,callback) {
let params = {
TableName:tableName,Item:{
"id": requestID,"name": requestID
}
};
return docclient.put(params,callback).promise();
}
这是此 lambda 执行的 CloudWatch 输出:
2021-04-16T22:24:49.754+02:00 START RequestId: 8766c005-c1f3-42fb-aee9-9e8352da67ed Version: $LATEST
2021-04-16T22:24:50.464+02:00 2021-04-16T20:24:50.464Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO Processing new assigned drill performance event. RequestID: 8766c005-c1f3-42fb-aee9-9e8352da67ed
2021-04-16T22:24:50.523+02:00 2021-04-16T20:24:50.523Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO { Records: [ { eventID: 'e09a400894c178ef66840f54e71b6c26',eventName: 'INSERT',eventVersion: '1.1',eventSource: 'aws:dynamodb',awsRegion: 'us-west-2',dynamodb: [Object],eventSourceARN: 'arn:aws:dynamodb:us-west-2:900776852541:table/fdrillPerformance-y22t7izqyvb2xiruvbf4zhadvm-fissiondev/stream/2021-04-15T21:48:06.158' } ] }
2021-04-16T22:24:50.541+02:00 2021-04-16T20:24:50.541Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO { ApproximateCreationDateTime: 1618604689,Keys: { id: { S: '7321fcf5-fed2-402a-b1cf-7667e958f73a' } },NewImage: { createdAt: { S: '2021-04-16T20:24:49.077Z' },touches: { N: '25' },__typename: { S: 'fdrillPerformance' },drillID: { S: '01adc7e6-67be-4bdf-828b-36833cbd7070' },targetUserID: { S: 'd4a95710-c4fb-4f0f-8355-76082e41c43a' },id: { S: '7321fcf5-fed2-402a-b1cf-7667e958f73a' },updatedAt: { S: '2021-04-16T20:24:49.077Z' } },SequenceNumber: '218143500000000009280556527',SizeBytes: 269,StreamViewType: 'NEW_IMAGE' }
2021-04-16T22:24:50.561+02:00 2021-04-16T20:24:50.561Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO { createdAt: { S: '2021-04-16T20:24:49.077Z' },updatedAt: { S: '2021-04-16T20:24:49.077Z' } }
2021-04-16T22:24:50.581+02:00 2021-04-16T20:24:50.581Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO CreatedAt date: Fri Apr 16 2021 20:24:49 GMT+0000 (Coordinated Universal Time)
2021-04-16T22:24:50.581+02:00 2021-04-16T20:24:50.581Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO Querying for daily statistics |16.3.2021| userID: |d4a95710-c4fb-4f0f-8355-76082e41c43a| drillID: |01adc7e6-67be-4bdf-828b-36833cbd7070| from table PersonalDrillDailyStatistic-y22t7izqyvb2xiruvbf4zhadvm-fissiondev
2021-04-16T22:24:50.784+02:00 2021-04-16T20:24:50.783Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO Found daily stats object
2021-04-16T22:24:50.784+02:00 2021-04-16T20:24:50.784Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO DailyStats ID found. Updating existing with touches 25
2021-04-16T22:24:50.784+02:00 2021-04-16T20:24:50.784Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO Updating daily stats (with id) item... ab00afe1-ed4b-4895-b1bb-31ac570fe46d 25
2021-04-16T22:24:50.883+02:00 2021-04-16T20:24:50.883Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO Found daily stats object
2021-04-16T22:24:51.302+02:00 2021-04-16T20:24:51.302Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO Success updating daily stats 01adc7e6-67be-4bdf-828b-36833cbd7070 d4a95710-c4fb-4f0f-8355-76082e41c43a 25
2021-04-16T22:24:51.401+02:00 2021-04-16T20:24:51.384Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO ConditionalCheckFailedException: The conditional request Failed at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/json.js:52:27) at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20) at Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:78:10) at Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:688:14) at Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10) at AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12) at /var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10 at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9) at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:690:12) at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:116:18) { code: 'ConditionalCheckFailedException',time: 2021-04-16T20:24:51.383Z,requestId: 'H8PC553OQABGJKR9KFIJMR1EHJVV4KQNSO5AEMVJF66Q9ASUAAJG',statusCode: 400,retryable: false,retryDelay: 36.82252581429517 }
2021-04-16T22:24:51.462+02:00 2021-04-16T20:24:51.462Z 8766c005-c1f3-42fb-aee9-9e8352da67ed INFO Success creating processed request 8766c005-c1f3-42fb-aee9-9e8352da67ed
2021-04-16T22:24:51.482+02:00 END RequestId: 8766c005-c1f3-42fb-aee9-9e8352da67ed
2021-04-16T22:24:51.482+02:00 REPORT RequestId: 8766c005-c1f3-42fb-aee9-9e8352da67ed Duration: 1728.11 ms Billed Duration: 1729 ms Memory Size: 128 MB Max Memory Used: 91 MB Init Duration: 413.89 ms
所以,你可以看到,虽然lambda被调用了一次,但它的某些部分被调用了两次,我不知道如何处理。我还需要更新每周和每月的统计数据,所以问题会变得更加复杂。在最后一个请求 ID 上使用 ConditionExpression 进行破解有效,但我会确保我的函数中的代码不会被调用两次,而不是尝试执行此类变通方法。
解决方法
根据@Jason Wadsworth 的评论,我通过删除回调函数更改了 $table->unique(["submission_id","user_id"]);
函数中的所有函数调用。请注意,我在 dynamo 相关函数中留下了回调参数,例如 exports.handler
- 没有它,由于某种原因代码没有写入数据库。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。