Multithreading MongoDB:实现读/写锁(互斥锁)
我需要使用MongoDB实现一些锁定机制,以防止数据不一致,但允许脏读 条件:Multithreading MongoDB:实现读/写锁(互斥锁),multithreading,mongodb,locking,mutex,mongodb-query,Multithreading,Mongodb,Locking,Mutex,Mongodb Query,我需要使用MongoDB实现一些锁定机制,以防止数据不一致,但允许脏读 条件: 只有在没有读取锁和无写入锁的情况下,才能获取写入锁 只有在没有写入锁的情况下,才能获取读取锁 单个文档上可以有多个并行的读取锁 必须有某种超时机制:如果(无论出于何种原因)某个进程没有释放其锁,应用程序必须能够恢复 只需忽略查询中的所有锁,即可实现脏读 (缺少写入进程不是本主题的一部分) 为什么读取和写入锁定/为什么不只使用写入锁定: 假设我们有两个集合:联系人和类别。这是一个n-m关系,其中每个联系人都有一个类
- 只有在没有
读取
锁和无
写入
锁的情况下,才能获取
写入
锁
- 只有在没有
锁的情况下,才能获取写入
锁读取
- 单个文档上可以有多个并行的
锁读取
- 必须有某种超时机制:如果(无论出于何种原因)某个进程没有释放其锁,应用程序必须能够恢复
写入进程不是本主题的一部分)
为什么读取
和写入
锁定/为什么不只使用写入
锁定:
假设我们有两个集合:联系人
和类别
。这是一个n-m关系,其中每个联系人都有一个类别ID数组
读取
锁定:在向联系人添加类别时,我们必须确保该类别当前未被删除(这需要写入
锁定,请参见下文)。而且,由于同一文档上可能存在多个读取
锁,因此多个进程可以将此单一类别添加到多个联系人中
写入
锁定:删除类别时,必须首先从所有联系人中删除类别ID。在运行此操作时,我们必须确保无法将此类别添加到任何联系人(此操作需要读取
锁定)。之后,我们可以安全地删除类别文档
这样,将始终存在一致的状态
超时时间:
这是最难的部分。我已经尝试过两次了,但总是发现一些问题,似乎很难解决
基本思想:每个获取的锁都有一个时间戳,直到该锁有效。如果这个时间戳是过去的,我们可以忽略这个锁。当一个进程完成它的任务时,它应该移除它的锁
最大的挑战是,拥有多个READ
锁,其中每个READ
锁都有自己的超时,但多个READ
锁可以具有相同的超时值。当释放READ
锁时,它必须只释放自身,所有其他READ
锁必须保留
我最后一次实施:
lock.read
可以包含元素,或者可以设置lock.write
。一定不可能两个都设置好
查询:
对此的查询还可以,有些查询可能会更简单一些(特别是“释放读锁”)。但向你们展示它们的主要原因是我仍然不确定我是否错过了什么
前言:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
ISODate(“现在”)
是当前时间。它用于忽略所有过期的锁。它还用于删除所有过期的读锁
ISODate(“锁过期”)
用于指示此锁何时过期,可以忽略/删除。(例如,现在+5秒
)
- 这在获取新锁时使用
- 当释放读锁时也会用到它
获取读取锁定:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
如果没有有效的写锁,则插入读锁
update(
{
_id: 1234,
$or: [
{ 'lock.write': null },
{ 'lock.write': { $lt: ISODate("now") } }
]
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
获取写入锁定:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
如果没有有效的读锁和写锁,则设置写锁
update(
{
_id: 1234,
$and: [
$or: [
{ 'lock.read':{ $size: 0 } },
{ 'lock.read':{ $not: { $gte: ISODate("now") } } }
],
$or: [
{ 'lock.write': null },
{ 'lock.write': { $lt: ISODate("now") } }
]
]
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
释放读取锁定:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
使用获取的读锁的过期时间戳移除读锁
update(
{
_id: 1234,
'lock.read': ISODate("lock expiration")
},
{
$unset: { 'lock.read.$': null }
}
)
update(
{
_id: 1234,
},
{
$pull: { 'lock.read': { $lt: ISODate("now") } }
}
)
update(
{
_id: 1234
},
{
$pull: { 'lock.read': null }
}
)
(如果多个进程获取了一个read
锁,则lock.read
数组可能包含多个相同的时间戳。尽管我们只需要删除一个时间戳,这不适用于$pull
,但可以使用位置操作符$
。
此外,我还通过附加更新删除了所有过期的锁。我尝试了一些方法,但无法将其减少到2个甚至1个更新。)
释放写入锁定:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
删除写日志。这里应该没有什么要检查的
update(
{
_id: 1234
},
{
$set: { 'lock.write': null }
}
)
编辑1:简化的获取读取和写入查询
{$not:{$gte:ISODate(“now”)}
仅在字段不包含任何内容时匹配$gte:ISODate(“now”)
。尽管它将匹配null
和不存在的字段以及空数组
获取读取锁定:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
获取写入锁定:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
但是仍然不知道关于“ReleaseREAD
lock”查询
我考虑过某种具有超时时间戳和锁计数的元组。但是问题出现在AcquireREAD
lock查询中
编辑2:不同的数据结构便于发布READ
lock
这是可行的,因为ObjectId
由时间戳、机器id、进程id和计数器组成。这样就不可能创建多个相等的对象。长话短说:
在获取读取
锁时,我们插入一个由超时时间戳和唯一的ObjectId
组成的文档。当释放它时,我们使用这个组合将它从阵列中移除。因此,唯一有趣的查询是:
获取写入锁定:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
释放读取锁定:
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } }
},
{
$set: { 'lock.write': null },
$push: { 'lock.read': ISODate("lock expiration") }
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: ISODate("now") } },
'lock.read': { $not: { $gte: ISODate("now") } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
'lock.write': { $not: { $gte: 4 } },
'lock.read.timeout': { $not: { $gte: 4 } }
},
{
$set: {
'lock.read': [],
'lock.write': ISODate("lock expiration")
}
}
)
update(
{
_id: 1234,
},
{
$pull: {
'lock.read': {
$or: [
{ 'timeout': ISODate("lock expiration"), process: ObjectId("...") },
{ 'timeout': { $lt: ISODate("now") } }
]
}
}
}
)
正如您所看到的,我们现在只需要一个查询就可以在清理所有超时锁时移除锁
唯一的进程标识符非常重要,因为