RavenDB如何正确并发更新同一文档:并行调用同一API端点仅新增单条附件元数据
问题描述
我开发了一个带有upload端点的C# REST API,该端点的唯一用途是处理二进制文件,并将其元数据(以Attachment模型的形式)添加到另一个实体的List<Attachment>属性中。
正常的串行调用表现
当我在Web应用中按如下伪代码所示的顺序调用该端点时,端点可按预期工作:处理每个二进制文件,并为指定实体添加单个Attachment记录。
const attachments = [Attachment, Attachment, Attachment]; for(const attachment of attachments) { await this.api.upload(attachment); }
异常的并行调用表现
但当我尝试按如下伪代码所示的并行方式上传附件时,每个二进制文件都能被正确处理,但实体中仅新增了一个Attachment元数据对象。
const attachments = [Attachment, Attachment, Attachment]; const requests = attachments.map((a) => this.api.upload(a)); await Promise.all(requests);
端点核心简化逻辑
该端点的核心逻辑大致如下(已做简化):
var attachment = new Attachment() { // 从上传的二进制表单文件中解析填充元数据 }; using (var session = Store.OpenAsyncSession()) { var entity = await session.LoadAsync<Entity>(entityId); entity.Attachments.Add(attachment); await session.StoreAsync(entity); await session.SaveChangesAsync(); };
问题初步定位
我怀疑问题出在端点被同时调用的并发冲突上:两个请求同时打开数据库会话,将对应实体加载到内存中,各自向内存实体副本的Attachments列表添加对应的Attachment对象,再将修改后的实体更新回数据库。最终数据库中保存的是最后完成(即耗时最长的那个)请求的结果,其他请求的修改都会被覆盖。
问题复现实验
我搭建了可复现该问题的示例,运行后可以在公共测试RavenDB实例上查看生成的实体:
- 打开名为
Hogwarts的数据库,再查看联系人Harry Potter,可以看到新增了两个附件(对应串行测试的结果) - 查看联系人
Hermione Granger时,仅新增了一个附件(Second.txt),而预期应该新增两个附件。
核心诉求
解决这个问题的最佳方案是什么?我不希望将文件批量发送到该端点,感谢任何帮助!
附:你可能需要手动点击运行按钮运行示例。由于测试服务器会定期清空数据,如果不存在对应数据库,可以手动创建名为
Hogwarts的数据库。另外由于这属于竞态条件,有时两个Attachment都能被正常添加,因此可能需要多次运行示例才能复现异常。
内容的提问来源于stack exchange,提问作者Tom Aalbers
相关产品推荐
相关产品推荐

