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

Objectify: Handle race condition to prevent duplicate account creation

我正在使用 Google App Engine 和 Datastore with objectify。

我正在尝试为用户(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()

我收到一个错误 "Only ancestor queries are allowed inside transactions",但我没有看到其他方法

这样做正确吗?

谢谢

在这种情况下,获得强一致性的最佳方法是通过键检索实体。

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

模式有点棘手。有一些关于它的讨论here。伪代码的基本思想是:

  • 开始交易
  • 加载具有唯一 PK 的实体
  • 如果有一个实体
    • 中止和 return 重复错误
  • 其他
    • 创建具有唯一 PK 的实体(+ 您需要的任何额外工作)
    • 提交交易
    • 如果提交失败
      • 中止和 return 重复错误
    • 其他
      • 一切都很好!

您可能不想让 username 成为 User 实体的主键,因此创建一个单独的 Username 实体并混合创建 UsernameUser 在同一笔交易中。一定要留下 Username 实体;这就是保证唯一性的原因。

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