【问题标题】:Google app engine: how to handle concurrency (racing condition)谷歌应用引擎:如何处理并发(竞速条件)
【发布时间】:2013-01-27 10:26:49
【问题描述】:

我正在尝试解决基于this 的赛车问题,以防止重复用户注册。因此,如果帐户存在或电子邮件已被使用,则不会创建任何实体。

@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:事务中只允许祖先查询。出了什么问题?

  2. 代码是否正确?我的意思是,它真的能解决赛车问题吗?

谢谢。

【问题讨论】:

    标签: google-app-engine


    【解决方案1】:

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

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

    对于 1),问题在于您在事务中执行常规查询,这无法按上述说明工作。 2)的答案就变成了no,query不能解决赛车问题,你需要显式gets。

    您需要一个会员、电子邮件和 SSN 模型。这是一个未经测试的快速示例,希望能助您一臂之力:

    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) 装饰器组合起来是否有效,如果可以,请尝试使用哪种顺序。

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

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

    虽然这最终的代码行数比您预期的要多,但它在概念上很简单直接,当您稍后再看它时,您可以很容易地弄清楚发生了什么。

    【讨论】:

    • 事情这么复杂?耶稣!非常感谢,testdal。因此,您将关键属性拆分为多个模型并构建交叉引用:Member 中有 email,Email 中有 member。但在我之前的问题中,为简洁起见,我省略了另一个属性 SSN。所以实际上有三个字段必须是唯一的。那我该如何放置交叉引用呢?三个模型,每个模型都包含另外两个关键属性?
    • 刚想出一个主意。假设我将帐户、电子邮件和 SSN 连接成一个长字符串,并将其作为实体的 id(或键名)。通过取出查询部分,函数get_or_insert2(id)的主要部分变为“entity = Member.get_by_id(id)”。这行得通吗?
    • 这个新想法行不通,因为它模拟的是 AND 条件,而不是 OR 条件。
    • 交叉引用只是为了确保您可以轻松找到相应的用户或电子邮件地址。如果您还需要检查 SSN,我建议您制作 SSN 模型,该模型也指向 User(并且 User 指向 SSN)。我可以更新示例。虽然有更多模型,但它在概念上仍然是直截了当的。
    • 感谢您的宝贵时间。我试试看。
    猜你喜欢
    • 1970-01-01
    • 2016-07-14
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 2019-06-07
    • 2010-10-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多