Google Datastore 中的原子序列计数器

Posted

技术标签:

【中文标题】Google Datastore 中的原子序列计数器【英文标题】:Atomic sequence counters in Google Datastore 【发布时间】:2017-11-13 12:00:09 【问题描述】:

我们有一个在 Google App Engine 中运行的应用程序,作为其操作的一部分,它会生成序列号。这些数字必须满足以下条件:

它们必须在指定的开始和结束范围之间 它们必须是唯一的(在某些情况下直到到达范围的末尾,在这种情况下我们可以再次从序列的开头开始) 它们必须是连续的(随机数不好,即使它们满足其他两个条件)

我们确实编写了试图确保生成的数字在全球范围内唯一的代码,但我无法共享该代码,因为 a) 它相当复杂,b) 它是我雇主的财产,c) 它不会出现在重负荷下工作。

由于未能确保我们满足独特的标准,我已经做了一些阅读,并找到了一些关于 sharded counters 的信息,但是虽然我认为这种方法可能会有所帮助,但我仍然认为它不能保证我们 100% 肯定会产生独特的序列。我的怀疑是,当涉及到 upserts 时,Datastore 中存在一些延迟,并且计数器更新与后续读取中反映的更新之间的延迟是罪魁祸首。此外,没有处理分片的 php 示例(尽管我们可能可以从其他示例中弄清楚,如果有一个有效的 PHP 示例会很好)。

我对这个问题提出的解决方案如下:

在 Memcache(支持整数的原子递增)和 Datastore(用于持久性)中的计数器中维护当前值。我们也可能会尝试对 Datastore 中的计数器进行分片。 当来自给定序列的新号码请求进来时: 检查 Memcache 的当前值。如果数据不在 Memcache 中,请从 Datastore 填充它 在 Memcache 中进行原子增量 为我们的进程使用从 Memcache 返回的值 将新的计数器值写回 Datastore 以确保持久性

从表面上看,这似乎是一个合理的解决方案,但我担心仍然存在一些极端情况,即我们最终会得到不一致的计数器值,尤其是在同时发生大量更新的情况下。虽然 Memcache 将确保返回值的原子性,但我不确定对 Datastore 的写入是否会按照请求的顺序发生,并且在它的末尾,Datastore 可能不会反映 Memcache 中的值,如果应用程序出现故障,并且在恢复服务时从 Datastore 加载了不正确的值。

数据存储区写入是否按接收顺序应用?我能否保证在执行所有写入后 Datastore 中的值与 Memcache 中的值匹配?有没有更好的解决方案(除了切换到支持自动增量/序列的 SQL 数据库)?

【问题讨论】:

【参考方案1】:

AppEngine 数据存储区中的顺序 ID 是一个难以解决的问题。

如果您尝试对数据存储进行持久性一一进行,您将达到事务吞吐量限制。

我能想到的最佳方法:

    使用分片来保留最后 N 个计数器值。 开始交易 在更新数据存储分片之前锁定 memcache 条目(您可以在 Go 中使用 nds 包或类似方式)并通过键从数据存储中获取所有分片值。基本上在 Go 中,只需为所有分片调用 nds.GetMulti()。锁应该有合理的过期值。对于 nds 包,需要 30 秒。 获取具有最大值的分片的分片 ID 和值。 更新适当的分片值首先在数据存储中 - 仅将单个实体存储到 db。例如,您可以有 10 个编号为 0-9 的分片,其中分片编号对应于计数器值的最后一位。所以 23 将映射到分片 3。 提交事务(退出事务函数/上下文) (使用 memcache CAS(比较和交换)操作来更新适当的 memcache 值或将其从 memcache 中删除。虽然正确地做这件事并非易事。你可以阅读我在 nds 包上开始的讨论 - https://github.com/qedus/nds/issues/58

基本上,您可以完全不使用 memcache,但 memcache 会为您省钱,并且可能会减少延迟(或不减少延迟)。

重要的一点是先锁定 memcache 并更新数据存储,然后从 memcache 中删除或使用 CAS 更新。你的计划正好相反。

【讨论】:

抱歉,我们没有使用 Go,所以我们不能使用 NDS 库。我们也许可以推出我们自己的 PHP 版本 没关系,我不是 NDS 库的作者。但它可以给你一些想法和陷阱。

以上是关于Google Datastore 中的原子序列计数器的主要内容,如果未能解决你的问题,请参考以下文章

Google App Engine Datastore 中的索引和索引条目限制

Google App Engine 中的 Datastore 与 Cloud SQL

是否可以删除命名空间中的所有Google Cloud Datastore Kinds?

Google App Engine / Datastore / Flask / Python app 中的内存泄漏

Google Cloud Datastore 中的 ROW_NUMBER

如何在 Google Cloud Datastore 上更新交易中的帐户余额