Lle*_*lyn 2 mongodb optimistic-concurrency
我正在模拟multiple concurrent requestMongoDB 的“更新”。
事情是这样的,我amount=1000在 mongoDB 中插入一条数据,每次触发 api 时,它都会更新金额并将amount += 50其保存回数据库。基本上它是一个find and update对单个文档的操作。
err := globalDB.C("bank").Find(bson.M{"account": account}).One(&entry)\n\n if err != nil {\n panic(err)\n }\n\n wait := Random(1, 100)\n time.Sleep(time.Duration(wait) * time.Millisecond)\n\n //step 3: add current balance and update back to database\n entry.Amount = entry.Amount + 50.000\n err = globalDB.C("bank").UpdateId(entry.ID, &entry)\nRun Code Online (Sandbox Code Playgroud)\n\n这里是该项目的源代码。
\n\n我正在使用 Vegeta 模拟请求:
\n\n如果我设置-rate=10(意味着一秒触发api 10次,所以1000 + 50 * 10 = 1500),数据是正确的
echo\xc2\xa0"GET\xc2\xa0http://localhost:8000"\xc2\xa0|\xc2\xa0\\\nvegeta\xc2\xa0attack\xc2\xa0-rate=10\xc2\xa0-connections=1\xc2\xa0-duration=1s\xc2\xa0|\xc2\xa0\\\ntee\xc2\xa0results.bin\xc2\xa0|\xc2\xa0\\\nvegeta\xc2\xa0report\nRun Code Online (Sandbox Code Playgroud)\n\n\n\n但与-rate=100(这意味着每秒触发 api 100 次,所以 1000 + 50 * 100 = 6000)会产生非常混乱的结果。
echo\xc2\xa0"GET\xc2\xa0http://localhost:8000"\xc2\xa0|\xc2\xa0\\\nvegeta\xc2\xa0attack\xc2\xa0-rate=100\xc2\xa0-connections=1\xc2\xa0-duration=1s\xc2\xa0|\xc2\xa0\\\ntee\xc2\xa0results.bin\xc2\xa0|\xc2\xa0\\\nvegeta\xc2\xa0report\nRun Code Online (Sandbox Code Playgroud)\n\n\n\n简而言之,我想知道的是:我认为 MongoDB 正在使用optimistic concurrency control,这意味着是否有write conflict,它应该重试一次,这样延迟就会上升,但应该保证数据是正确的。
为什么结果看起来MongoDB中数据的正确性完全无法保证?
\n\n41我知道你们中的一些人可能会注意到 line和处的睡眠42,但即使我将其注释掉,当我测试时-rate=500结果时仍然不正确。
有任何线索为什么会发生这种情况吗?
\n一般来说,您应该将代码的相关部分提取到问题中。让人们在你的 76 行程序中找到 5 条相关的行是不体贴的。
您的测试正在执行并发查找和修改操作。假设有两个并发进程 A 和 B,每个进程将帐户余额增加 50。起始余额为 0。操作顺序可以是:
A: what is the current balance for account 1234?
B: what is the current balance for account 1234?
DB -> A: balance for account 1234 is 0
DB -> B: balance for account 1234 is 0
A: new balance is 0+50 = 50
A: set balance for account 1234 to 50
DB -> A: ok, new balance for account 1234 is 50
B: new balance is 0+50 = 50
B: set balance for account 1234 to 50
DB -> B: ok, new balance for account 1234 is 50
Run Code Online (Sandbox Code Playgroud)
从数据库的角度来看,这里不存在“写冲突”。您两次要求将给定帐户的余额设置为 50。
解决这个问题有不同的方法。一种是使用条件更新,过程如下所示:
A: what is the current balance for account 1234?
B: what is the current balance for account 1234?
DB -> A: balance for account 1234 is 0
DB -> B: balance for account 1234 is 0
A: new balance is 0+50 = 50
A: if balance in account 1234 is 0, set balance to 50
DB -> A: ok, new balance for account 1234 is 50
B: new balance is 0+50 = 50
B: if balance in account 1234 is 0, set balance to 50
DB -> B: balance is not 0, no update was performed
B: err, let's start over
B: what is the current balance for account 1234?
DB -> B: balance for account 1234 is 50
B: new balance is 50+50 = 100
B: if balance in account 1234 is 50, set balance to 100
DB -> B: ok, new balance for account 1234 is 100
Run Code Online (Sandbox Code Playgroud)
如您所见,数据库必须支持条件更新,应用程序必须处理并发更新的可能性并重试操作。
如果余额可以上下波动,那么这并不是编写借方和贷方系统的实际有用方法(但如果余额只能增加或只能减少,那么实际上效果很好)。在实际系统中,您将使用一个特殊字段,其目的是识别应用程序检索某些数据时存在的文档的特定版本;更新的条件是文档的当前版本保持不变,并且每次更新都会增加版本。然后将检测到并发更新,因为版本号错误而不是内容字段错误。
有多种方法可以在数据库端产生“写入冲突”,例如使用 MongoDB 4.0+ 支持的事务。原则上,它的工作方式相同,但“版本”被称为“事务标识符”,并且它存储在不同的位置(不是内嵌在正在操作的文档中)。但原理是一样的。在这种情况下,数据库会通知您存在写入冲突,您仍然需要重新发出操作。
更新:
我认为你还需要区分“乐观货币管制”的概念、它的实施以及实施的适用范围。https://docs.mongodb.com/manual/faq/concurrency/#how-capsular-are-locks-in-mongodb例如说:
对于大多数读写操作,WiredTiger 使用乐观并发控制。WiredTiger 仅在全局、数据库和集合级别使用意向锁。当存储引擎检测到两个操作之间存在冲突时,其中一个操作将引发写入冲突,导致 MongoDB 透明地重试该操作。
仔细看这句话,适用于存储引擎层面的写操作。我想当 MongoDB 执行类似$set或其他原子写入操作时,这将适用。但这不适用于您在示例中给出的应用程序级操作序列。
如果您使用您最喜欢的关系 DBMS 尝试示例代码,我想您会发现它产生的结果与您在 MongoDB 中看到的结果大致相同,如果您围绕每个单独的读取和写入发出一个事务(这样可以平衡读取和写入)位于不同的事务中),出于同样的原因 - RDBMS 在事务的生命周期内锁定数据(或使用 MVCC 等技术),但不会跨事务。
同样,如果您将同一帐户上的余额读取和余额写入放入 MongoDB 中的事务中,您可能会发现当其他事务同时修改相关帐户时,您会收到暂时性错误。
最后,这里描述了 MongoDB 为事务(带重试)实现的 API 。如果仔细观察,您会发现它期望应用程序不仅重新发出事务提交命令,而且重复整个事务操作。这是因为,通常,如果存在“写入冲突”,则起始数据已更改,并且仅再次尝试最终写入是不够的 - 应用程序中的计算可能需要重做,甚至可能会导致该过程的副作用发生变化一个结果。
| 归档时间: |
|
| 查看次数: |
6523 次 |
| 最近记录: |