GAE:数据存储一致性,同时避免争用(即没有实体组)



我正在GAE中构建一个投票网站,它的工作原理如下:

  • 投票被宣布为开放(但只开放一两分钟)
  • 人们投票
  • 投票结束。不能再投票了
  • 显示结果

投票阶段只持续一两分钟,因此会在短时间内投出大量选票。

我想避免数据存储争用,所以我不能将投票存储在实体组中(它可能会超过每秒1次写入的限制)。

但是,一旦投票结束,我必须确保我计算所有选票。

我的问题是:一旦投票结束,我如何确保投票的数据存储一致性(没有实体组)?换句话说,在什么时候我才能确保每一张选票都已写入数据存储区(并且可以从中读取)?

只有当我知道每一张选票都可读时,我才能安全地计算结果。

附言:我应该注意,这不是一个"简单"的投票方案;选民选择他们的第一、第二和第三选择,获胜者是由一个相当复杂的迭代过程决定的,即不足以计算每个候选人的票数。

提前感谢!

我的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()

在第一种方法中,我们只编写实体。通过这种方式,我们不使用事务,因此不会锁定所有根实体。我们只是写作。我们也可以做一个最终一致性的get,只是为了检查并保存进一步的写入。是的,这是个问题。在这两者之间可能会发生多次写入,并且值始终是最后一次写入的值。

方法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,后来我只是检查一个属性是否为"set"。根据情况,我们是否保存。当心并发请求可能已经更改了属性vote_selection并且已经设置了它

对此的一些想法:

通过使用user_id,我知道只有相同的用户并发请求才会触发这种行为。

基本上,如果用户发起两个并发的vote_selection请求,那么请求将发生变化:

  • 两者都检查实体Vote是否存在
  • 将插入实体
  • 另一个将获得实体

但可能他们两个都会将选择属性视为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,直到每个事务完成。如果当前正在发生一个或多个事务,则任何其他事务都将重试。

明智地选择,我希望看到更多的答案/意见/方法。

看看Sharding Counters,它是GAE设计模式,适用于在短时间内对实体组进行大量写入的场景。

最新更新