我必须以“强烈单调增长”的方式标记某些东西。无论是发票号码,还是运送标签号码等。
花哨的说法:我需要数1,2,3,4 ...... 我可用的空间数通常是100.000个数字,我每天需要1000个。
我知道这在分布式系统中是一个难题,而且我们通常会更好地使用GUID。但在这种情况下,出于法律原因,我需要“传统编号”。
这可以在Google AppEngine上实现(最好是用Python)吗?
答案 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,并从它们的范围内服务,直到它们用完值。
快速算法:
n
。将分片开始设置为检索到的值加1并结束检索到的加号n
。这可以很好地扩展,但是,你可以超过的数量是分片数乘以你的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(出于法律原因)。