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
实体并混合创建 Username
与 User
在同一笔交易中。一定要留下 Username
实体;这就是保证唯一性的原因。
这个问题(唯一性)实际上是像GAE数据存储这样的大规模分布式系统中更具技术挑战性的问题之一。仅当传统RDBMS是单主系统时,在传统RDBMS中解决起来很简单,从而对可伸缩性和容错性产生影响。 GAE 为您提供必要的原语来强制执行集群范围内的唯一性;它们只是不太容易使用。
我正在使用 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
实体并混合创建 Username
与 User
在同一笔交易中。一定要留下 Username
实体;这就是保证唯一性的原因。
这个问题(唯一性)实际上是像GAE数据存储这样的大规模分布式系统中更具技术挑战性的问题之一。仅当传统RDBMS是单主系统时,在传统RDBMS中解决起来很简单,从而对可伸缩性和容错性产生影响。 GAE 为您提供必要的原语来强制执行集群范围内的唯一性;它们只是不太容易使用。