在 Azure Cosmos DB 中维护分布式增量计数器

Kun*_*pta 6 azure azure-cosmosdb

我对 cosmos DB 相当陌生,并试图了解azure cosmos DB SDK 为 Java 提供的用于修补文档的增量操作。我需要在容器中的文档之一中维护增量计数器。该文件看起来像这样——

{"counter": 1}
Run Code Online (Sandbox Code Playgroud)

现在,在我的应用程序中,每次发生操作时,我希望将此计数器的值增加 1。为此,我使用CosmosPatchOperations。我在这里添加一个增量,这样cosmosPatch.increment("/counter", 1)效果很好。

现在,该应用程序可以运行多个实例,所有实例都与 Cosmos 容器中的同一文档进行通信。所以App1和App2都可以同时触发增量。SDK 方法返回更新后的文档,我需要使用该更新后的值。

我的问题是,这里的 cosmos DB 是否采用某种锁定机制来确保两个补丁相继发生,并且在这种情况下,我在 App1 和 App2 中获得的更新值是什么(SDK 方法返回更新后的值)文档)。其中一个是 2 个,另一个是 3 个吗?

Couchbase 在集群级别支持这样的计数器,如此处所述它对我来说工作得很好,没有任何并发​​问题。我现在正在迁移到 cosmos Db,并一直在努力寻找如何实现这一目标。

更新1:

我决定测试一下。我在本地 Mac 中设置了 Cosmos 模拟器,并创建了一个 DB 和容器,并且 RU 会自动从 1 增加到 10K。然后在这个容器中我添加了一个像这样的文档 -

{
"id": "randomId",
"counter": 0
}
Run Code Online (Sandbox Code Playgroud)

在此之后,我创建了一个简单的 API,其职责只是在每次调用时将计数器加 1。然后我使用Locust多次调用这个 API 来模拟一个小型的类似负载的场景。最初,测试运行良好,每次调用都像预期的那样接收计数器(以增量方式)。在增加负载时,我看到一些错误,即RequestTimeOutException,状态代码为 408。其他请求仍然可以正常工作,并获得正确的计数器值。我不明白是什么导致了这里的 RequestTimeOut 异常。堆栈跟踪暗示与并发有关,但我无法理解它。这是堆栈跟踪 -

在此输入图像描述

更新 2: 更新 1 中的测试运行是在我的本地计算机上完成的,我意识到我的本地计算机上可能存在资源问题,导致这些错误。决定在预生产环境中使用实际的 cosmos DB 而不是模拟器来测试这一点。

测试配置-

  1. 具有 RU 的 Cosmos DB 容器可自动从 400 扩展到 4000
  2. 2 个应用程序实例共享负载。
  3. 用于摄取应用程序负载的 Locust 脚本

发现-

直到约 170 TPS,一切都运行顺利。除此之外,我注意到属于 2 个不同桶的错误 -

  1. "exception": "["请求率较大。可能需要更多请求单元,因此未进行任何更改。请稍后重试此请求。了解更多信息:http://aka.ms/cosmosdb-error-429"]"。

我不确定 170 个奇怪的补丁操作如何会耗尽 4000 个 RU,但这完全是一个不同的讨论。

  1. "exception": "["已尝试对资源进行冲突请求。重试以避免冲突。“]”,状态代码 449。

此错误清楚地表明 cosmos DB 不处理并发请求。我想了解他们是否在内部维护一个队列来处理某些请求,或者他们根本不处理任何并发写入。

Saj*_*ran 5

PATCH 与其他操作没有什么不同,CosmosDB 从根本上实现了乐观并发控制,这与具有这些机制的关系数据库不同。乐观并发控制 (OCC) 允许您防止更新丢失并保持数据正确。OCC可以通过文档的etag来实现。Azure Cosmos DB 中的每个文档都有一个E_TAG属性。

在您的场景中,是的,如果两者都成功,它将在其中一个返回 2 ,在另一个返回 3 ,因为 SDK 有重试机制,并在此处进行了解释。另请查看此示例

如果 Azure Cosmos DB 帐户配置了多个写入区域,则冲突和冲突解决策略适用于文档级别,最后写入优先 (LWW) 是默认冲突解决策略

  • 对我来说似乎不太一样。通常,您指定要设置的确切值,但使用“increment”,您发送一个“函数”,并让数​​据库端处理修改值的操作。我已经在具有 1000 个“并发”请求的单个写入区域上尝试过此操作,它在不使用“if-match”标头的情况下返回了预期值。 (2认同)