Google app engine 谷歌应用引擎:并发用户注册

Google app engine 谷歌应用引擎:并发用户注册,google-app-engine,concurrency,Google App Engine,Concurrency,我知道这是一个经典的问题,但我仍然不知道怎么做。在GoogleAppEngine上,我有一个成员注册表单,它使用jQuery检查用户名是否存在。 当然存在并发问题:几个用户尝试注册,输入相同的用户名,验证发现用户名可用,并允许他们几乎同时按“添加”。验证不会检测到这一点。在我的应用程序中,用户名、电子邮件和个人ID都应该是唯一的。如何防止以下代码出现并发问题: member = Member() member.username = self.request.get('username') mem

我知道这是一个经典的问题,但我仍然不知道怎么做。在GoogleAppEngine上,我有一个成员注册表单,它使用jQuery检查用户名是否存在。 当然存在并发问题:几个用户尝试注册,输入相同的用户名,验证发现用户名可用,并允许他们几乎同时按“添加”。验证不会检测到这一点。在我的应用程序中,用户名、电子邮件和个人ID都应该是唯一的。如何防止以下代码出现并发问题:

member = Member()
member.username = self.request.get('username')
member.Pid = self.request.get('Pid')
member.email = self.request.get('email')
...

这是memcache的一个很好的用途。Ajax验证函数应该在memcache中输入一个条目,以记录已请求的用户名。它还应该检查memcache和数据存储,以确保用户名是免费的。同样,注册码应该检查memcache,以确保当前用户是请求用户名的用户


这很好地解决了您的并发问题,最好的事情是memcache中的条目自行过期,无论是在定时的基础上,还是当缓存太满时。

由于唯一性约束是在用户名上,您必须将其用作数据存储中的密钥并使用事务

def txn():
    key = ndb.Key(Member, username)
    member = key.get()
    if member is not None:
        raise CustomAlreadyExistsException(member)  # This will abort txn
    member = Member(
        id=username,
        Pid=self.request.get('Pid'),
        email=self.request.get('email'),
        ...)
    member.put()
ndb.transaction(txn)
这确保只有一个人可以注册用户名

jQuery助手将检查
ndb.Key(Member,userid).get()
是否给出结果。GET不是事务性的

为了提高客户端在检查可用性后“保留”用户名的可用性,您可以按照Daniel的建议使用memcached,但我会打电话给YAGNI,跳过复杂性,让一些人在提交表单后出现验证错误。请注意,memcached是最好的,对任何事情都没有保证

如果需要保证多个字段的唯一性,则必须为它们添加模型类,并签入跨组(XG)事务

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

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

class Member(ndb.Model):
    pid = ndb.KeyProperty()
    email = ndb.KeyProperty()

    @property
    def pid_value(self):
        return self.pid.id()

    @property
    def email_value(self):
        return self.email.id()

def txn():
    member_key = ndb.Key(Member, username)
    pid_key = ndb.Key(PersonalId, self.request.get('Pid'))
    email_key = ndb.Key(Email, self.request.get('email'))

    member, pid, email = ndb.get_multi([member_key, pid_key, email_key])

    if member is not None or pid is not None or email is not None:
        raise CustomAlreadyExistsException(member, pid, email)  # This will abort txn

    # Create instances referencing each other
    email = Email(key=email_key, member=member_key)
    pid = Pid(key=pid_key, member=member_key)
    member = Member(
        key=member_key,
        pid=pid_key,
        email=email_key,
        ...)
    ndb.put_multi([member, pid, email])

ndb.transaction(txn, xg=True)
我同意特斯达尔的观点。
如果您仍然想实现Daniel建议的memcache,那么应该执行类似“memcache.add(usernameA,dummy value,short period);”这样的操作。所以你知道usernameA被保留了很短的一段时间,并且不会与“memcache.add(usernameB,…”

谢谢。我会尝试一下。我有两个问题。1)当用户a注册时,程序会执行“memcache.add('username',usernameA)”,当用户B注册时,“memcache.add('usernameB',usernameB)”不会覆盖usernameA?2) 如何将用户名与某个用户关联,以便我们知道谁想要什么用户名?谢谢。您应该在添加之前检查memcache。您可以尝试在Ajax调用和注册本身的同时传递一个(可能是随机的)唯一ID。丹尼尔,你能再详细说明一下吗?特别是关于我的两个问题。我仍然没有一个清晰的概念。我正在努力实现你的建议。但有一条错误消息:BadRequestError:多个实体组上的事务只允许使用高复制数据存储。我认为ndb是HRD?我该如何解决这个问题呢?顺便说一句,我在谷歌应用程序引擎的应用程序设置中检查了我上传的应用程序。“数据存储复制选项:”中的项目显示“高复制”,因此错误只发生在SDK环境中?dev_appserver.py--高复制,非常感谢。另一个问题:我想先获取用户名、pid和电子邮件。我是否可以将txn定义为“def txn(用户名、pid、电子邮件):”然后通过“ndb.transaction(txn(用户名、pid、电子邮件),xg=True)”调用该函数?因为如果这样做,我会在调用时收到一条错误消息:TypeError:“NoneType”对象不可调用.ndb.transaction(lambda:txn(用户名、pid、电子邮件),xg=True)