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

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

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

花哨的说法:我需要计算1,2,3,4 …我可用的数字空间通常是100.000个数字,我需要大概1000个一天。

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

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

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

如果您放弃必须严格按顺序的要求,则可以使用分层分配scheme。 基本思想/限制是事务不能影响多个存储组。

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

gaetk – Google AppEngine工具包现在提供了一个简单的库函数来按顺序获取数字。 它基于尼克·约翰逊的交易方法,可以很容易地用作Martin vonLöwis分拆方法的基础:

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

function基本上是这样实现的:

 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) 

看看分片柜台是如何制作的。 它可以帮助你。 你也真的需要他们是数字。 如果唯一令人满意的只是使用实体键。

如果你对顺序不是太严格,你可以“分解”你的发言者。 这可以被认为是“最终连续”的计数器。

基本上,你有一个是“主”计数的实体。 然后你有一些实体(根据你需要处理的负载)有自己的计数器。 这些碎片保留了大师的ID块,并从它们的范围中提供出来,直到它们耗尽了价值。

快速algorithm:

  1. 你需要得到一个ID。
  2. 随机挑选一个碎片。
  3. 如果碎片的开始小于结束,则开始并递增。
  4. 如果碎片的开始等于(或者更多的哦,哦)它的结束,去主人,采取的价值,并增加一个数量n 。 设置分片开始到检索值加1,结束到检索加n

这可以很好地扩展,但是,你可以排除的数量是碎片的数量乘以你的n值。 如果你希望你的logging出现上涨,这可能会起作用,但如果你想让他们代表秩序,这将是不准确的。 同样重要的是要注意最新的值可能有漏洞,所以如果你使用它来扫描某些原因,你将不得不介意的差距。

编辑

我需要这个为我的应用程序(这就是为什么我search问题:P),所以我已经实施了我的解决scheme。 它可以抓取单个ID以及有效地抓取批次。 我已经在一个受控的环境(在appengine上)testing了它,它performance得非常好。 你可以在github上find代码。

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

我为我的博客实现了一些非常简单的事情,增加了一个IntegerProperty,而不是Key ID。

我定义max_iden()来查找当前正在使用的最大的iden整数。 此function扫描所有现有的博客文章。

 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 

然后,当创build一个新的博客文章,我分配一个max_iden() + 1max_iden() + 1属性

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

我想知道在这之后是否还想添加某种validationfunction,也就是说,为了确保max_iden()现在已经递增,在移动到下一个发票之前。

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

或者,你可以使用allocate_ids(),正如人们所build议的,然后创build这些实体(即占位符属性值)。

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

然后,在创build新发票时,您的代码可以运行这些条目以find具有最低ID的一个,以便占位符属性尚未被真实数据覆盖。

我没有把这一点付诸实践,但似乎理论上应该起作用,很可能是人们已经提到的同样的局限性。

我正在考虑使用以下解决scheme:使用CloudSQL(MySQL)插入logging并分配顺序ID(可能与任务队列),稍后(使用Cron任务)将logging从CloudSQL移回数据存储区。

这些实体也可以有一个UUID,所以我们可以在CloudSQL中映射数据存储中的实体,并且也有顺序的ID(出于合法的原因)。