Google App Engine:如何处理并发(竞赛条件)

Google App Engine:如何处理并发(竞赛条件)

问题描述:

我正在尝试基于以防止重复的用户注册.因此,如果该帐户存在或已使用电子邮件,则不会创建任何实体.

I am trying to solve the racing problem based on this to prevent duplicate user registrations. So if the account exists or the email has been used, no entity will be created.

@ndb.transactional
def get_or_insert2(account, email):
    accountExists, emailExists = False, False
    entity = Member.get_by_id(account)
    if entity is not None:
        accountExists = True
    if Member.query(Member.email==email).fetch(1):
        emailExists = True
    if not accountExists and not emailExists:
        entity = Member(id=account)
        entity.put()
    return (entity, accountExists, emailExists)

我的问题:

  1. 我收到一条错误消息:BadRequestError:在事务内部仅允许祖先查询.有什么问题吗?

  1. I got an error message: BadRequestError: Only ancestor queries are allowed inside transactions. what was the problem?

代码正确吗?我的意思是,这真的可以解决赛车问题吗?

Is the code correct? I mean, can it really solve the racing problem?

谢谢.

交易处理实体组,并且在跨组事务中最多可以包含5个实体组.实体组由单个服务器(或复制的组)处理,这意味着在实体组内检查数据或执行祖先查询时,它可以具有一致的内部状态.

Transactions work on entity groups, and you can include up to 5 entity groups in a cross group transaction. An entity group is handled by a single server (or group, replicated), which means it is able to have consistent internal state when checking data or doing ancestor queries within the entity group.

常规查询是全局的,对最终一致性的索引.您不知道何时所有节点的所有更改都已包含在索引中.您无法锁定整个数据存储区以获取事务的一致快照状态.如果您习惯于为查询建立一致的索引,则这与常规RDBMS的主要区别在于.

Regular queries are global, on indexes with eventual consistency. You don't know when all changes from all nodes have been included in an index. You can't lock up the entire datastore to get consistent snapshot state for your transaction. This is a key difference from a regular RDBMS if you're used to consistent index for queries.

对于1),问题在于您正在事务内部执行常规查询,但如上所述无法正常工作. 2)的答案变为否,查询无法解决竞速问题,您需要显式获取.

For 1), the problem is that you're doing a regular query inside a transaction, which doesn't work as explained above. The answer to 2) then becomes no, query can't solve racing problem, you need explicit gets.

您将需要一个用于会员,电子邮件和SSN的模型.这是一个未经测试的快速示例,希望可以助您一臂之力:

You will need a Model for Member, Email and SSN. This is a quick untested example that hopefully gets you going:

class Member(ndb.Model):
    email = ndb.KeyProperty()
    ssn = ndb.KeyProperty()
    # More user properties goes here...

class Email(ndb.Model):
    member = ndb.KeyProperty()

class SSN(ndb.Model):
    member = ndb.KeyProperty()

@ndb.tasklet
def get_or_insert2(account, email, ssn):
    created = False
    member_key = ndb.Key(Member, account)
    email_key = ndb.Key(Email, email)
    ssn_key = ndb.Key(SSN, ssn)
    member_obj, email_obj, ssn_obj = yield ndb.get_multi_async([member_key, email_key, ssn_key])

    if member_obj is None and email_obj is None and ssn_obj is None:
        member_obj = Member(key=member_key, email=email_key, ssn=ssn_key))
        email_obj = Email(key=email_key, member=member_key)
        ssn_obj = SSN(key=ssn_key, member=member_key)
        yield ndb.put_multi_async([member_obj, email_obj])
        created = True

    raise ndb.Return([created, member_obj, email_obj, ssn_obj])

outcome = ndb.transaction(lambda: get_or_insert2(account, email, ssn), xg=True)

我不确定是否可以将@ ndb.tasklet和@ ndb.transactional(xg = True)装饰器组合使用,如果可以,请按哪个顺序试一下.

I'm not sure if it works to combine @ndb.tasklet and @ndb.transactional(xg=True) decorators, and if so, which order, just try it out.

如果您需要基于电子邮件或ssn查询用户,则可以例如将KeyProperties重命名为* _ref并进行类似的操作

If you need to query User based on email or ssn, you could for example rename the KeyProperties to *_ref and make something like

@ndb.ComputedProperty
def email(self):
    return self.email_ref.id()

尽管这最终比预期的要多得多的代码行,但从概念上讲它是简单而直接的,并且当您稍后再返回时,可以很容易地弄清楚发生了什么.

While this ends up being more lines of code than you anticipated, it is conceptually simple and straight forward, and you can easily figure out what's going on when you get back to it later.