我正在尝试创建一个唯一的计数器表。为了更新表中的计数器,我尝试使用FindOneAndUpdate。起初,一切似乎都按计划进行,因此我认为我会进行压力测试。我创建了一个获取计数器20,000的进程,并在5个线程上执行了该计数器。果然,我最终得到了重复。 我正在针对单个本地数据库运行它;但是生产数据库是副本集。
static void Main(string[] args)
{
Console.WriteLine("Press Enter to begin...");
Console.ReadLine();
int numOfThreads = 10;
var tasks = new List<Task>();
for (int i = 0; i < numOfThreads; i++)
tasks.Add(Task.Run(() => TestWrites()));
Console.WriteLine($"Waiting for {tasks.Count} tasks to complete...");
Task.WaitAll(tasks.ToArray());
Console.WriteLine("Done. Press Enter to exit.");
Console.ReadLine();
}
private static void TestWrites()
{
var tester = new ConcurrentTester();
int writes = 20000;
for (int i = 0; i < writes; i++)
tester.InsertCounter(tester.GetCounter());
Console.WriteLine($"Wrote {writes}");
}
public class ConcurrentTester
{
private IMongoDatabase database;
public ConcurrentTester()
{
var connStr = "";
var mongoClient = new MongoClient(connStr);
database = mongoClient.GetDatabase("test");
}
public int GetCounter()
{
var collection = database.GetCollection<InternalCounter>("Concurrency");
var filter = Builders<InternalCounter>.Filter.Eq("CounterName", "Test");
var mkt = collection.Find(filter).FirstOrDefault();
var options = new FindOneAndUpdateOptions<InternalCounter> { IsUpsert = true, ReturnDocument = ReturnDocument.After };
InternalCounter ctn = collection.FindOneAndUpdate(filter,
Builders<InternalCounter>.Update.Inc(s => s.CounterValue, 1), options);
return ctn.CounterValue;
}
}
public class InternalCounter
{
public InternalCounter()
{
_id = ObjectId.GenerateNewId();
}
public object _id { get; set; }
public string CounterName { get; set; }
public int CounterValue { get; set; }
}
更新
由于此代码已在我们的服务中,因此我添加了一个锁来防止并发调用。直到我弄清楚MongoDb发生了什么,这只是一个停顿。
lock (sync)
{
ctn = collection.FindOneAndUpdate(filter,
Builders<InternalCounter>.Update.Inc(s => s.CounterValue, 1), options);
}
更新2
我看到也有一个findAndModify方法,但是它没有在C#库中实现。从它的描述来看,它听起来应该像findOneAndUpdate一样。 我还读到findOneAndUpdate取代了findAndModify。这是真的吗?
更新3 经过进一步考虑,我的锁无法使用。当只有一个实例但具有负载平衡,有多个实例并且无法用锁修复时,它将起作用。