我有这样的文件
public class SomeDocument
{
public Guid Id { get; set; }
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
现在我有两个不同的服务(A和B),它们适当地更新PropertyA和PropertyB并以异步方式工作。这意味着我不知道哪些服务将首先完成,应该创建文档以及谁应该更新它。
因此,要更新(或创建)我目前在服务A中使用此类代码的文档
var filter = new FilterDefinitionBuilder<SomeDocument>().Where(r => r.Id == id);
var options = new FindOneAndUpdateOptions<SomeDocument, SomeDocument>() { IsUpsert = true };
var update = Builders<SomeDocument>.Update.Set(r => r.PropertyA, "Property A value");
await Database.GetCollection<SomeDocument>("someDocuments").FindOneAndUpdateAsync(filter, update, options);
以及服务B的下一个代码
var filter = new FilterDefinitionBuilder<SomeDocument>().Where(r => r.Id == id);
var options = new FindOneAndUpdateOptions<SomeDocument, SomeDocument>() { IsUpsert = true };
var update = Builders<SomeDocument>.Update.Set(r => r.PropertyB, "Property B value");
await Database.GetCollection<SomeDocument>("someDocuments").FindOneAndUpdateAsync(filter, update, options);
一切看起来都很好,但有时当两个服务同时工作时我会收到下一个错误
Unhandled Exception: MongoDB.Driver.MongoCommandException: Command findAndModify failed: E11000 duplicate key error collection: someDocuments index: _id_ dup key: { : BinData(3, B85ED193195A274DA94BC86B655B4509) }.
at MongoDB.Driver.Core.WireProtocol.CommandWireProtocol`1.ProcessReply(ConnectionId connectionId, ReplyMessage`1 reply)
at MongoDB.Driver.Core.WireProtocol.CommandWireProtocol`1.<ExecuteAsync>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at MongoDB.Driver.Core.Servers.Server.ServerChannel.<ExecuteProtocolAsync>d__26`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at MongoDB.Driver.Core.Operations.CommandOperationBase`1.<ExecuteProtocolAsync>d__29.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at MongoDB.Driver.Core.Operations.WriteCommandOperation`1.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at MongoDB.Driver.Core.Operations.FindAndModifyOperationBase`1.<ExecuteAsync>d__19.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at MongoDB.Driver.OperationExecutor.<ExecuteWriteOperationAsync>d__3`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at MongoDB.Driver.MongoCollectionImpl`1.<ExecuteWriteOperationAsync>d__62`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at CVSP.MongoDbStore.MongoDbWriteModelFacade.<AddRecordField>d__6.MoveNext() in D:\Projects\Test\Source\MongoDbStore\WriteModel\MongoDbWriteModelFacade.cs:line 58
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c.<ThrowAsync>b__6_1(Object state)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
在这种情况下我应该如何插入/更新文件?
更新
扩展成功了
public static async Task<TProjection> FindOneAndUpdateWithConcurrencyAsync<TDocument, TProjection>(this IMongoCollection<TDocument> collection, FilterDefinition<TDocument> filter, UpdateDefinition<TDocument> update, FindOneAndUpdateOptions<TDocument, TProjection> options = null, CancellationToken cancellationToken = default(CancellationToken))
{
try
{
return await collection.FindOneAndUpdateAsync(filter, update, options, cancellationToken);
}
catch (MongoException ex)
{
Thread.Sleep(10);
return await collection.FindOneAndUpdateAsync(filter, update, options, cancellationToken);
}
}
在第一次龟头上使用try / catch看起来很奇怪,我从一开始就不喜欢它,但在阅读https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/#upsert-and-unique-index之后,所有疑问都消失了。
答案 0 :(得分:2)
嗯,这是同步问题,遗憾的是没有简单的解决方案。为了找到一个黑客,让我们分析后端可能发生的事情。
假设我们有两个线程(服务)试图插入文档。
t1: 00:00:00.250 -> find document with Id (1)
t2: 00:00:00.255 -> find document with id (1)
t1: 00:00:00.260 -> No document found
t2: 00:00:00.262 -> No document found
t1: 00:00:00.300 -> Insert a document with Id(1)
t2: 00:00:00.300 -> Insert a document with Id(1)
宾果......我们得到了例外。两个线程都试图插入具有相同id的文档。
我们能在这做什么?
让我们把这个短篇小说变成我们的优势。捕获此异常并再次尝试调用upsert。这一次,它将成功找到文档并进行更新。
我修改了ServiceA
nd ServiceB
的代码,如下所示,并尝试在紧密循环中插入10000个文档:
public async Task ServiceA(Guid id)
{
var filter = new FilterDefinitionBuilder<SomeDocument>().Where(r => r.Id == id);
var update = Builders<SomeDocument>.Update.Set(r => r.PropertyA, "Property A value");
var options = new UpdateOptions() { IsUpsert = true };
var database = _client.GetDatabase("stackoverflow");
var collection = database.GetCollection<SomeDocument>(CollectionName,
new MongoCollectionSettings
{
WriteConcern = WriteConcern.W1
});
await collection.UpdateOneAsync(filter, update, options);
}
public async Task ServiceB(Guid id)
{
var filter = new FilterDefinitionBuilder<SomeDocument>().Where(r => r.Id == id);
var update = Builders<SomeDocument>.Update.Set(r => r.PropertyB, "Property B value");
var options = new UpdateOptions() { IsUpsert = true };
var database = _client.GetDatabase("stackoverflow");
var collection = database.GetCollection<SomeDocument>(CollectionName,
new MongoCollectionSettings
{
WriteConcern = WriteConcern.W1
});
await collection.UpdateOneAsync(filter, update, options);
}
这是我的语言代码。不完美但有用。
for (var i = 0; i < 10000; i++)
{
var _guid = Guid.NewGuid();
var _tasks = new[]
{
new Task(async (x) =>
{
var p = new Program();
try
{
await p.ServiceA(Guid.Parse(x.ToString()));
}
catch (MongoWriteException me)
{
await Task.Delay(5);
await p.ServiceA(Guid.Parse(x.ToString()));
}
}, _guid),
new Task(async (x) =>
{
var p = new Program();
try
{
await p.ServiceB(Guid.Parse(x.ToString()));
}
catch (MongoWriteException me)
{
await Task.Delay(5);
await p.ServiceB(Guid.Parse(x.ToString()));
}
}, _guid)
};
_tasks[0].Start();
_tasks[1].Start();
Task.WaitAll(_tasks);
}