我在应用程序中获得了一个我想使用Mongo(3.6)作为多线程锁(在不同服务器上)的位置。本质上类似“如果一个线程开始工作,其他线程应该通过mongo看到它,并且不要并行开始相同的工作”。
从我学到的documentation
结合“多数”写关注,“可线性化”读关注使多个线程可以在单个文档上执行读写操作,就像单个线程实时执行这些操作一样。
这对我来说听起来不错,如果一个线程开始工作,我会插入某个文档,而其他线程会检查该文档是否已经存在,如果不存在,则不要启动,但是对于我的情况不起作用。
我准备了两个测试-一个非并行成功阻塞了第二个线程-但并行测试失败,我得到了两个RebuildLog
文档。
using System;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
namespace FindOneAndUpdateTests
{
public class FindOneAndUpdateTests
{
private static IMongoDatabase GetDatabase()
{
var dbName = "test";
var client = new MongoClient("mongodb://localhost:45022");
return client.GetDatabase(dbName);
}
private IMongoCollection<RebuildLog> GetCollection()
{
return GetDatabase().GetCollection<RebuildLog>("RebuildLog");
}
[Fact]
public async Task FindOneAndUpdate_NotParallel_Test()
{
var dlpId = Guid.NewGuid();
var first = await FindOneAndUpdateMethod(dlpId);
var second = await FindOneAndUpdateMethod(dlpId);
first.Should().BeFalse();
second.Should().BeTrue();
}
[Fact]
public async Task FindOneAndUpdate_Parallel_Test()
{
var dlpId = Guid.NewGuid();
var taskFirst = FindOneAndUpdateMethod(dlpId);
var taskSecond = FindOneAndUpdateMethod(dlpId);
var first = await taskFirst;
var second = await taskSecond;
first.Should().BeFalse();
second.Should().BeTrue();
}
private async Task<bool> FindOneAndUpdateMethod(Guid dlpId)
{
var mongoCollection = GetCollection();
var filterBuilder = Builders<RebuildLog>.Filter;
var filter = filterBuilder.Where(w => w.DlpId == dlpId);
var creator = Builders<RebuildLog>.Update
.SetOnInsert(w => w.DlpId, dlpId)
.SetOnInsert(w => w.ChangeDate, DateTime.UtcNow)
.SetOnInsert(w => w.BuildDate, DateTime.UtcNow)
.SetOnInsert(w => w.Id, Guid.NewGuid());
var options = new FindOneAndUpdateOptions<RebuildLog>
{
IsUpsert = true,
ReturnDocument = ReturnDocument.Before
};
var result = await mongoCollection
.WithWriteConcern(WriteConcern.WMajority)
.WithReadConcern(ReadConcern.Linearizable)
.FindOneAndUpdateAsync(filter, creator, options);
return result != null;
}
}
[BsonIgnoreExtraElements]
public class RebuildLog
{
public RebuildLog()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public DateTime ChangeDate { get; set; }
public string ChangeUser { get; set; }
public Guid DlpId { get; set; }
public string Portal { get; set; }
public DateTime? BuildDate { get; set; }
}
}
我的怀疑是,我对原子手工制作的GetOrInsert的想法(请参阅带有IsUpsert的FindOneAndUpdate)打破了文档中“在单个文档上”的约束。有解决这个问题的主意吗?还是不可能?
答案 0 :(得分:0)
这很有趣。可能您在DlpId上没有唯一索引吗?这就是为什么mongo决定不需要顺序执行这些操作的原因,因为在您的情况下,这不是写后读模式(如“客户会话和因果一致性保证”中所述)。它是同时进行两次更新或创建的。 那这个呢? :
public class SyncDocument
{
// ...
[BsonElement("locked"), BsonDefaultValue(false)]
public bool Locked { get; set; }
}
在客户端代码中:
var filter = Builders<SyncDocument>.Filter.Eq(d => d.Locked, false);
var update = Builders<SyncDocument>.Update.Set(d => d.Locked, true);
var result = collection.UpdateOne(filter, update);
if (result.ModifiedCount == 1) {
Console.WriteLine("Lock acquired");
}
“具有锁定的文档”字段应在应用程序启动之前创建(如果适用于您的任务)。