Objectify:处理竞争条件以防止重复创建帐户

时间:2015-02-17 00:44:41

标签: google-app-engine google-cloud-datastore objectify

我正在使用Google App Engine和数据存储区进行客观化。

我正在尝试为用户(Google用户)创建一个帐户,因此我需要检查用户是否存在,如果没有,请为该用户创建一个帐户,但我面临的事实是如果我使用createAccount API方法垃圾邮件

,则会创建两次帐户
@ApiMethod(name = "account.google.create")
public Account createGoogleAccount(final User user) throws OAuthRequestException {
    if (user == null) {
        throw new OAuthRequestException("createAccount: OAuthRequestException<User is not authenticated>");
    }
    Account alreadyExisting = RObjectifyService.getObjectify().load().type(Account.class).filter("accountId.GOOGLE", user.getUserId()).filter("email", user.getEmail()).first().now();
    if (alreadyExisting != null) {
        throw new OAuthRequestException("createAccount: OAuthRequestException<Account already exist>");
    }
    return RObjectifyService.getObjectify().transactNew(new Work<Account>() {
        @Override
        public Account run() {
            Account account = AccountProvider.createAccountFromGoogleProvider(user);
            RObjectifyService.save(account);
            return account;
        }
    });
}

我读到我应该使用交易,但我不能,因为如果我在交易中这样做:

RObjectifyService.getObjectify().load().type(Account.class).filter("accountId.GOOGLE", user.getUserId()).filter("email", user.getEmail()).first().now()

我收到错误“在事务中只允许祖先查询”,但我没有看到另一种方法来执行此操作

这是正确的方法吗?

由于

2 个答案:

答案 0 :(得分:1)

您需要一个交易,并且您需要一个实体,其主键是您尝试使其唯一的值(即用户名)。

这种模式有点棘手。对它进行了一些讨论here。伪代码的基本思想是:

  • 启动交易
  • 使用唯一PK加载实体
  • 如果有实体
    • 中止并返回重复错误
  • 否则
    • 使用独特的PK(+无论您需要多少额外工作)创建实体
    • 提交交易
    • 如果提交失败
      • 中止并返回重复错误
    • 否则
      • 一切都很棒!

您可能不希望username成为User实体的主键,因此请创建一个单独的Username实体并混合创建Username在同一个交易中使用User。一定要离开Username实体;这就是保证唯一性的原因。

这个问题(唯一性)实际上是GAE数据存储区等大规模分布式系统中技术上更具挑战性的问题之一。仅当传统的RDBMS是单主系统时,在传统的RDBMS中解决起来很简单,从而对可扩展性和容错性产生影响。 GAE为您提供必要的原语,以强制实现群集范围内的唯一性;它们不是很容易使用。

答案 1 :(得分:0)

在这种情况下,具有强一致性的最佳方法是按键检索实体。