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

时间:2014-02-26 17:12:20

标签: google-app-engine google-cloud-datastore consistency

我正在GAE中建立一个投票网站,其工作方式如下:

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

投票阶段只持续一两分钟,因此很多投票将在短期内投票。

我想避免数据存储区争用,因此我无法将投票存储在实体组中(它可能会超过〜1次写入/秒限制)。

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

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

只有一次我知道每一个投票都是可读的,我能否安全地计算结果。

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

提前致谢!

2 个答案:

答案 0 :(得分:2)

我的2c。

假设您使用用户服务。我将使用投票处理程序和choice作为用户的输入。我不会使用祖先所以所有的选票都是根实体。我们可以将user_id用作唯一的用户,作为投票的关键。

现在根据表现我们有3个选择。为什么?让我们来看看。

前两个。

方法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请求,那么请求会发生变化:

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

但也许两者都会将选择属性视为无,并且两者都会尝试写入。 最后一个是有效的。并且您将有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()

在这种情况下,一切顺利但我们锁定根实体投票直到每个事务完成。如果当前正在发生一个或多个事务,则任何其他事务将重试。

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

答案 1 :(得分:2)

查看Sharding Counters,它是GAE设计模式,适用于在实体组上短时间内需要大量写入的情况。