【问题标题】:AWS Lambda sequential problem using DynamoDB使用 DynamoDB 的 AWS Lambda 顺序问题
【发布时间】:2019-07-20 01:08:05
【问题描述】:

我正在尝试实现一个由 API Gateway 调用的函数。它传递了一个电子邮件地址+密码,然后检查该电子邮件地址是否已在使用中。如果不是这样,它应该放在我的发电机数据库表中。

当使用已使用的电子邮件地址对其进行测试时,尽管布尔值应设置为 true,但仍会执行 put 操作。

'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

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

【问题讨论】:

  • 我假设您正在对密码进行哈希处理,但只是没有将其包含在您的代码示例中。
  • 您将电子邮件地址称为maileMailAddressEmailAddress。我会选择一个并使用它,让您的生活更轻松!

标签: javascript amazon-web-services aws-lambda amazon-dynamodb aws-api-gateway


【解决方案1】:

问题只是同步。

函数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 的 sn-p 修复您的代码后,您仍然可能遇到eventual consistency 问题。如果您想确保从表中读取最新的值,则必须在查询中使用 ConsistentRead 属性。

    请记住,DynamoDB 运行的这些复制通常快如闪电(大多数情况下它们只需要几百毫秒),但您最终可能会陷入一些灰色地带,然后您会想知道为什么您的代码不起作用。

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

    另外,请注意 Thomas Edwards 的回答:扫描操作非常昂贵(无论是性能还是成本方面)。您应该不惜一切代价avoid 他们并改用Global Secondary Indexes

    希望这会有所帮助!

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

    【讨论】:

    • 一切都对,除了我的昵称拼错 :-)
    • 我想我需要我的眼镜... :p 现在就修好它!感谢您指出!
    • 这很好,但仍然不够,因为读取和写入之间可能存在竞争条件。需要make the put conditional,以便 DynamoDB 本身是写入是否成功的仲裁者,消除了竞争条件......但这将是有问题的,在这里,电子邮件不是主键。
    【解决方案3】:

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

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

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

    【讨论】:

    • “还请记住,DynamoDB 保存记录可能需要一些时间,这就是您可能能够通过的原因。”,而不是这种情况。 ttuika 在这个问题上一针见血:一切都与同步有关。 OP 正在执行回调,就好像它们是同步的代码片段,从而导致不一致和奇怪的行为。
    猜你喜欢
    • 2018-11-22
    • 2016-11-21
    • 2022-09-23
    • 1970-01-01
    • 2021-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-20
    相关资源
    最近更新 更多