我的 2c。
假设您使用用户服务。我将使用一个投票处理程序和一个choice 作为用户的输入。我不会使用祖先,所以所有投票都将是根实体。我们可以使用唯一的用户 user_id 作为投票的键。
现在根据性能我们有 3 个选择。为什么?让我们来看看。
前 2 个。
方法 1 - 盲写(无事务)
class VoteHandler(webapp2.RequestHandler):
def get(self, choice):
user = users.get_current_user()
# Blind write it! or just check before if exists
Vote(id=user.user_id, selection=choice).put()
在第一种方法中,我们只编写实体。这样我们就不会使用事务,因此我们不会锁定所有的根实体。我们只是写。我们也可以做一个最终一致性的获取,只是为了检查并可能保存进一步的写入。是的,这是一个问题。其间可能会发生多次写入,并且值将始终是最后一次写入的值。
方法 2 - Get_or_inserts(小交易)
class VoteHandler(webapp2.RequestHandler):
def get(self, choice):
user = users.get_current_user()
# Construct a vote with the user_id as a key using get_or_insert
vote = Vote.get_or_insert(user.user_id())
# Check if he has voted (general check with default entity prop to None)
if vote.selection is not None:
# vote is cast return or do other logic
return
vote.selection = choice
vote.put()
然后知道user_id是投票的关键,你就可以得到强一致性的选票。
这样,如果需要,一位用户只有一票,可以选择一个或多个选项。
关于get_or_insert 它所做的是使用事务并像这样进行获取:
def txn(key_name, **kwds):
entity = Story.get_by_key_name(key_name, parent=kwds.get('parent'))
if entity is None:
entity = Story(key_name=key_name, **kwds)
entity.put()
return entity
def get_or_insert(key_name, **kwargs):
return db.run_in_transaction(txn, key_name, **kwargs)
get_or_insert('some key', title="The Three Little Pigs")
在第二种方法中,我在开始时使用了get_or_insert,后来我只是检查了一个属性是否“设置”。根据我们保存或不保存的条件。
谨防!!!并发请求可能已更改属性 vote_selection 并已设置它。
对此的一些想法:
通过使用user_id,我知道只有相同的用户并发请求才会触发此行为。
基本上,如果用户发起 2 个并发 vote_selection 请求,那么请求将发生变化:
- 两者都检查实体投票是否存在。
- 将插入实体。
- 另一方将获得实体。
但也许他们都将 selection 属性视为 None 并且都将尝试写入。
最后一个将是有效的。而且您将有 2 次或更多写入(如果有更多请求)。
方法 3 - 事务性
class VoteHandler(webapp2.RequestHandler):
def get(self, choice):
user = users.get_current_user()
self.vote(user.user_id, choice)
@ndb.transactional()
def vote(key, choice):
vote = ndb.Key(Vote, key).get()
if vote:
# user has voted
return
# return the key
return Vote(id=key, selection=choise).put()
在这种情况下一切顺利,但我们锁定根实体 Vote 直到每个事务完成。如果当前正在发生一个或多个事务,则任何其他事务都将重试。
明智地选择,我希望看到更多的答案/意见/方法。