如何在Google AppEngine上实现“自动增量”

时间:2010-10-21 09:02:16

标签: python database google-app-engine

我必须以“强烈单调增长”的方式标记某些东西。无论是发票号码,还是运送标签号码等。

  1. 一定数字不得使用两次
  2. 当使用所有较小的数字(无孔)时,应该使用每个数字。
  3. 花哨的说法:我需要数1,2,3,4 ...... 我可用的空间数通常是100.000个数字,我每天需要1000个。

    我知道这在分布式系统中是一个难题,而且我们通常会更好地使用GUID。但在这种情况下,出于法律原因,我需要“传统编号”。

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

9 个答案:

答案 0 :(得分:25)

如果您必须按顺序增加数字而没有间隙,则需要使用单个实体,在事务中更新该实体以“消耗”每个新数字。在实践中,你将被限制为每秒产生大约1-5个数字 - 这听起来对你的要求很好。

答案 1 :(得分:7)

如果删除ID必须严格按顺序排列的要求,则可以使用分层分配方案。基本思想/限制是事务不得影响多个存储组。

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

答案 2 :(得分:7)

gaetk - Google AppEngine Toolkit现在附带了一个简单的库函数来获取序列中的数字。它基于尼克约翰逊的交易方法,可以很容易地用作MartinvonLöwis的分片方法的基础:

>>> 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)

答案 3 :(得分:5)

如果你对顺序不太严格,你可以“加”你的增量器。这可以被认为是“最终顺序”的反击。

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

快速算法:

  1. 您需要获取身份证。
  2. 随机选择一个碎片。
  3. 如果碎片的开始小于其结束,请将其开始并递增。
  4. 如果分片的开始等于(或者更多哦哦)它的结束,请转到主分区,取值并为其添加一个量n。将分片开始设置为检索到的值加1并结束检索到的加号n
  5. 这可以很好地扩展,但是,你可以超过的数量是分片数乘以你的n值。如果您希望您的记录显示为上升,则可能会有效,但如果您希望它们代表订单则不准确。同样重要的是要注意最新值可能有漏洞,因此如果您出于某种原因使用它进行扫描,则必须注意间隙。

    修改

    我需要这个用于我的应用程序(这就是我在搜索问题的原因:P)所以我已经实现了我的解决方案。它可以获取单个ID以及有效地获取批次。我在受控环境(在appengine上)进行了测试,并且表现非常好。您可以找到代码on github

答案 4 :(得分:4)

看一下sharded counters是如何制作的。它可能会帮助你。你真的需要它们是数字。如果unique满足,只需使用实体键。

答案 5 :(得分:1)

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

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

然后,在创建新发票时,您的代码可以运行这些条目以查找ID最低的条目,以便占位符属性尚未被实际数据覆盖。

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

答案 6 :(得分:0)

请记住:分片会增加获得唯一自动增量值的概率,但不保证。如果你必须有一个独特的自动入侵,请听取尼克的建议。

答案 7 :(得分:0)

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

我定义max_iden()以查找当前使用的最大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

然后,在创建新博文时,我为其分配iden属性max_iden() + 1

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

我想知道您是否还想在此之后添加某种验证功能,即确保max_iden()现已增加,然后再转到下一张发票。

总之:脆弱,低效的代码。

答案 8 :(得分:0)

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

实体也可以拥有UUID,因此我们可以在CloudSQL中映射数据存储区中的实体,并且还具有顺序ID(出于法律原因)。

相关问题