使用DynamoDB的AWS Lambda顺序问题

问题描述 投票:0回答:3

我正在尝试实现API网关调用的函数。它会传递一个电子邮件地址+密码,然后检查电子邮件地址是否已被使用。如果不是这种情况,则应将其放入我的发电机数据库表中。

当使用已经在使用的电子邮件地址对其进行测试时,仍然会执行put操作,尽管boolean应该设置为true。

'use strict';

var AWS = require('aws-sdk'),
  uuid = require('uuid'),
  documentClient = new AWS.DynamoDB.DocumentClient();

exports.handler = function(event, context, callback) {

  if (event.body !== null && event.body !== undefined) {

    let body = JSON.parse(event.body);
    let eMailAddress = body.mail;
    let password = body.password;
    var EmailInUse = Boolean(false);

    var paramsScan = {
      TableName: "accounts"
    };
    documentClient.scan(paramsScan, function(err, data) {
      for (var i in data.Items) {
        i = data.Items;
        if (i.EmailAddress == eMailAddress) {
          console.log("already used");
          callback(err, "Email Address already in Use!");
          EmailInUse = true;
        }
      }

    });

    console.log(EmailInUse);
    if (EmailInUse == false) {
      console.log("should not enter if email used");
      var params = {
        Item: {
          "AccountID": uuid.v1(),
          "Password": password,
          "EmailAddress": eMailAddress
        },
        TableName: "accounts"
      };

      documentClient.put(params, function(err, data) {
        if (err) {
          callback(err, null);
        } else {
          const response = {
            statusCode: "200",
            "headers": {},
            body: JSON.stringify(params),
            "isBase64Encoded": "false"
          };
          callback(null, response);
        }
      });

    }
  }
};

这是我的Cloudwatch日志,使用相同的参数调用它2次:

12:54:01
START RequestId: 281b0eda-950b-40fc-a2e2-d326cd04f8a4 Version: $LATEST
12:54:01
2019-02-26T12:54:01.434Z 281b0eda-950b-40fc-a2e2-d326cd04f8a4 false
12:54:01
2019-02-26T12:54:01.471Z 281b0eda-950b-40fc-a2e2-d326cd04f8a4 should not enter if email used
12:54:01
END RequestId: 281b0eda-950b-40fc-a2e2-d326cd04f8a4
12:54:01
REPORT RequestId: 281b0eda-950b-40fc-a2e2-d326cd04f8a4 Duration: 320.98 ms Billed Duration: 400 ms Memory Size: 128 MB Max Memory Used: 31 MB
12:54:47
START RequestId: b9df94ce-0d59-4dfb-8b61-8098db566431 Version: $LATEST
12:54:47
2019-02-26T12:54:47.591Z b9df94ce-0d59-4dfb-8b61-8098db566431 false
12:54:47
2019-02-26T12:54:47.591Z b9df94ce-0d59-4dfb-8b61-8098db566431 should not enter if email used
12:54:47
2019-02-26T12:54:47.812Z b9df94ce-0d59-4dfb-8b61-8098db566431 already used
12:54:47
END RequestId: b9df94ce-0d59-4dfb-8b61-8098db566431
12:54:47
REPORT RequestId: b9df94ce-0d59-4dfb-8b61-8098db566431 Duration: 311.87 ms Billed Duration: 400 ms Memory Size: 128 MB Max Memory Used: 31 MB

看着这个我注意到在检查电子邮件地址是否已被使用之后,调用最后一个日志输出“已经使用”。有人能告诉我如何解决这个问题吗?提前谢谢了。

javascript amazon-web-services aws-lambda amazon-dynamodb aws-api-gateway
3个回答
4
投票

问题只是同步。

函数documentClient.scan在你的情况下使用回调。这意味着,在执行回调之前调用以下代码(console.log(EmailInUse);等)。

您可以将所有内容放入回调中,或者使用async/await,因为AWS Lambda支持Node.js 8.10:

var AWS = require('aws-sdk'),
  uuid = require('uuid'),
  documentClient = new AWS.DynamoDB.DocumentClient();

exports.handler = async event => {

  if (!event.body) return httpResponse(400, 'body is missing!');

  try {
    let body = JSON.parse(event.body);
    let eMailAddress = body.mail;
    let password = body.password;
    var EmailInUse = Boolean(false);

    var paramsScan = {
      TableName: "accounts"
    };
    const data = await documentClient.scan(paramsScan).promise();
    for (var i in data.Items) {
        i = data.Items;
        if (i.EmailAddress == eMailAddress) {
          console.log("already used");
          // you can just return here:
          //return httpResponse(200, "Email Address already in Use!"); 
          EmailInUse = true;
        }
    }

    console.log(EmailInUse);
    if (EmailInUse == false) {
      console.log("should not enter if email used");
      var params = {
        Item: {
          "AccountID": uuid.v1(),
          "Password": password,
          "EmailAddress": eMailAddress
        },
        TableName: "accounts"
      };

      await documentClient.put(params).promise();
      return httpResponse(200, JSON.stringify(params));
    }
  } catch (err) {
    return httpResponse(500, JSON.stringify(err));
  }
};

function httpResponse(statusCode, body) {
  return {
            statusCode,
            body,
            "isBase64Encoded": "false"
          };
}

您可以在找到电子邮件地址时完成该过程,然后您可以摆脱EmailInUse变量 - 它使您的代码更短,更简单,更容易推理。


2
投票

@ttulka的回答非常准确。

不过,我想在他的回答之上添加一些内容:

即使在回调 - 或异步/等待 - 整理后,您的代码仍然可能失败。那为什么呢?

DynamoDB是一个分布式系统。分布式系统本质上倾向于在其核心使用最终的一致性,而这正是DynamoDB默认的功能。

这意味着在使用@ttulka的代码片段修复代码后,您仍然可能会遇到eventual consistency问题。如果您想完全确定从表中读取最新值,则必须在查询中使用ConsistentRead属性。

请记住,DynamoDB运行的这些复制通常是闪电般快速的(大多数情况下它们只需要几百毫秒),但是你可能最终陷入某些灰色区域,然后你会想知道为什么你的代码没有'工作。

对于您的用例(检查现有的电子邮件),这应该没关系,因为两个人几乎不可能同时在同一封电子邮件中注册。但是,确保在处理关键数据(如银行账户)时,您应该始终支持ConsistentReads。与EventualConsistentReads相比,它们的成本是其两倍。

另外,请注意托马斯·爱德华兹的答案:扫描操作非常昂贵(性能和成本都很高)。你应该不惜一切代价avoid他们,而是使用Global Secondary Indexes

希望这可以帮助!

编辑:修正ttulka的昵称后他指出:)


1
投票

扫描是非常昂贵的,随着您的网站的增长,这将是非常有效的。

另外请记住,DynamoDB可能需要一些时间来保存记录,这就是您可能能够通过的原因。

如果要经常快速地搜索,请在DynamoDB中使用EmailAddress上的索引,或者找到另一种检查重复的方法。我有一个单独缓存的已注册电子邮件索引来检查速度。

© www.soinside.com 2019 - 2024. All rights reserved.