微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

我的 lambda 函数的一部分执行两次

如何解决我的 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 举报,一经查实,本站将立刻删除。