代码之家  ›  专栏  ›  技术社区  ›  max

如何在Google AppEngine上实现autoincrement

  •  31
  • max  · 技术社区  · 14 年前

    我得用“强烈单调递增”的方式给某件东西贴上标签。不管是发票号,发货标签号还是类似的。

    1. 一个数字不能用两次
    2. 当使用了所有较小的数字(没有孔)时,应使用每个数字。

    花哨的说法:我需要数一数,二,三,四。。。 我可用的数字空间通常是100000个数字,我每天大概需要1000个。

    这可以在Google AppEngine上实现吗(最好用Python实现)?

    9 回复  |  直到 14 年前
        1
  •  25
  •   Nick Johnson    14 年前

    如果您绝对必须有连续递增的数字,而没有间隔,则需要使用单个实体,在事务中更新该实体以“使用”每个新数字。在实践中,您将被限制为每秒生成1-5个数字,这听起来很适合您的需求。

        2
  •  7
  •   Martin v. Löwis    14 年前

    例如,假设您有“用户”的概念,那么可以为每个用户分配一个存储组(为每个用户创建一些全局对象)。每个用户都有一个保留ID的列表。为用户分配ID时,选择一个保留ID(在事务中)。如果没有id,则创建一个新事务,从全局池中分配100个id(例如),然后创建一个新事务,将其添加到用户中,同时撤消一个id。假设每个用户只按顺序与应用程序交互,则用户对象上不会有并发性。

        3
  •  7
  •   max    14 年前

    这个 gaetk - Google AppEngine Toolkit 现在提供了一个简单的库函数来获取序列中的数字。它是基于尼克·约翰逊的交易方法,并且可以很容易地被用作Martin von Lwis的分割方法的基础:

    >>> from gaeth.sequences import * 
    >>> init_sequence('invoce_number', start=1, end=0xffffffff)
    >>> get_numbers('invoce_number', 2)
    [1, 2]
    

    该功能基本上是这样实现的:

    def _get_numbers_helper(keys, needed):
      results = []
    
      for key in keys:
        seq = db.get(key)
        start = seq.current or seq.start
        end = seq.end
        avail = end - start
        consumed = needed
        if avail <= needed:
          seq.active = False
          consumed = avail
        seq.current = start + consumed
        seq.put()
        results += range(start, start + consumed)
        needed -= consumed
        if needed == 0:
          return results
      raise RuntimeError('Not enough sequence space to allocate %d numbers.' % needed)
    
    def get_numbers(needed):
      query = gaetkSequence.all(keys_only=True).filter('active = ', True)
      return db.run_in_transaction(_get_numbers_helper, query.fetch(5), needed)
    
        4
  •  5
  •   Kevin Cox    10 年前

    如果对序列不太严格,可以“切分”递增器。这可以被认为是一个“最终顺序”计数器。

    基本上,有一个实体是“master”计数。然后有许多实体(基于需要处理的负载)有自己的计数器。这些碎片从主服务器上保留大块的id,并在它们的范围之外服务,直到它们用完值。

    快速算法:

    1. 你需要一个身份证。
    2. 随便挑一块碎片。
    3. 如果碎片的开始小于它的结束,则取它的开始并递增。
    4. n 为了它。将碎片开始设置为检索值加1,结束设置为检索值加1 n个 .

    这可以很好地扩展,但是,你可以通过的数量是碎片的数量乘以你的 n个 价值。如果你想让你的记录看起来上升,这可能会起作用,但如果你想让他们代表顺序,这将是不准确的。还需要注意的是,最新的值可能有漏洞,因此如果出于某种原因使用该值进行扫描,则必须注意间隙。

    我的应用程序需要这个(这就是我搜索问题P的原因),所以我实现了我的解决方案。它可以抓取单个id,也可以有效抓取批处理。我在一个受控的环境中测试过它(在appengine上),它表现得非常好。你可以找到密码 on github .

        5
  •  4
  •   Ilian Iliev    14 年前

    看看 sharded counters 都是制造出来的。可能会对你有帮助。你真的需要它们是数字吗。如果unique满足要求,只需使用实体键。

        6
  •  1
  •   max_jf5    11 年前

    或者,您可以使用allocate_ids(),正如人们建议的那样,然后预先创建这些实体(即使用占位符属性值)。

    first, last = MyModel.allocate_ids(1000000)
    keys = [Key(MyModel, id) for id in range(first, last+1)]
    

    然后,当创建一个新的发票时,您的代码可以在这些条目中运行以找到ID最低的条目,这样占位符属性就不会被实际数据覆盖。

    我还没有付诸实践,但似乎它应该在理论上起作用,很可能与人们已经提到的限制相同。

        7
  •  0
  •   stevep    12 年前

    记住:分片增加了获得唯一的自动增值的可能性,但不能保证它。如果你必须有一个独特的自动增量,请接受尼克的建议。

        8
  •  0
  •   max_jf5    11 年前

    我为我的博客实现了一些非常简单的东西,它增加了一个IntegerProperty, iden 而不是密钥ID。

    max_iden() 找到最大值 当前使用的整数。此函数扫描所有现有的博客文章。

    def max_iden():
        max_entity = Post.gql("order by iden desc").get()
        if max_entity:
            return max_entity.iden
        return 1000    # If this is the very first entry, start at number 1000
    

    然后,当我创建一个新的博客文章时,我给它分配一个 财产 max_iden() + 1

    new_iden = max_iden() + 1
    p = Post(parent=blog_key(), header=header, body=body, iden=new_iden)
    p.put()
    

    我想知道您是否还想在这之后添加某种验证函数,即在转到下一张发票之前,确保max_iden()现在已经增加。

    总而言之:脆弱、低效的代码。

        9
  •  0
  •   estebarb    10 年前

    我正在考虑使用以下解决方案:使用CloudSQL(MySQL)插入记录并分配顺序ID(可能有任务队列),然后(使用Cron任务)将记录从CloudSQL移回数据存储。

    这些实体还可以有一个UUID,因此我们可以从CloudSQL中的数据存储映射实体,还可以有顺序ID(出于法律原因)。