【问题标题】:GAE: Datastore consistency whilst avoiding contention (i.e. without entity groups)GAE:数据存储一致性,同时避免争用(即没有实体组)
【发布时间】:2014-02-26 17:12:20
【问题描述】:

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

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

投票阶段只持续一两分钟,所以会在短时间内投出大量的票。

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

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

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

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

PS:我应该注意这不是一个“简单”的投票方案;选民选择他们的第 1、第 2 和第 3 选择,获胜者是由一个相当复杂的迭代过程决定的,即仅计算每个候选人获得了多少票是不够的。

提前致谢!

【问题讨论】:

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


    【解决方案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()
    

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

    方法 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 直到每个事务完成。如果当前正在发生一个或多个事务,则任何其他事务都将重试。

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

    【讨论】:

    • 谢谢吉米!那么你是说如果我知道投票实体的键(即本例中的 user_id),那么数据存储读取保证会给出最新(一致)的值? [如果不是这种情况,我如何确保在计算结果时阅读了每一张选票,即如何确定没有选票等待出现在我的数据存储实例中?]
    • @Justin 答案:是的,通过它的键获取实体将保证强一致性。
    • 吉米,出色的答案,非常感谢细节。我认为第二个选项对我来说是一种方式,因为任何选民都不应该同时投票超过一次,如果他们投票,他们所做的任何投票都是可以接受的。但是让这三个场景展示不同的方法真的很有帮助。
    【解决方案2】:

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

    【讨论】:

    • 我可以看到投票键的分片列表可能有效(计数器不足以满足我的用例)。值得深思 - 谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    • 1970-01-01
    • 1970-01-01
    • 2011-12-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多