从 C# 驱动程序建立多个快速连接时,服务器发送了一个无效的随机数
Server sent an invalid nonce when making multiple rapid connections from C# driver
我最近向我的开发数据库添加了身份验证,针对“admin”数据库进行身份验证,并在我的连接字符串中使用 username/password 组合,例如mongodb://username:password@server:27017
。几乎立即我开始看到连接无法打开,异常显示“服务器发送了一个无效的随机数”。为了尝试缓解这个问题,我查看了我的 IMongoClient 对象的生命周期,并从实例化许多此类对象转变为使用使用 Microsoft.Extensions.DependencyInjection 库注入到我的业务服务中的 Singleton。这并没有缓解问题。我使用 .AddSingleton<IMongoClient>(factory => new MongoClient(Configuration["Storage:MongoConnectionString"]))
在 Startup.cs 中设置了 MongoClient。我知道连接字符串是正确的,因为它在 MongoDB Compass 中工作,也因为通过驱动程序的前几次调用成功;当多个并发调用正在进行时,问题开始出现。
我在 .NET Core 3.1.2 下使用 MongoDB .NET 驱动程序,版本 2.11.0。问题出现在我的本地环境 运行 Windows 10,以及我在 VMware Photon 上 Docker 内的暂存环境 运行。
应用程序有两个组件与 MongoDB 建立连接,它们都是 ASP.Net 核心应用程序,其中一个服务于 API 用于我的应用程序的交互式使用,和一个 运行 用于后台处理的 Quartz 调度程序。我是 运行 MongoDB 4.4.0 社区,位于 Docker 容器中。
我对包括驱动程序的引用是:
<PackageReference Include="MongoDB.Bson" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.11.0" />
根据 this post on the MongoDB Jira site 的说法,我不是第一个遇到这个问题的人。 Mathias Lorenzen 在 Jira 上的问题中建议,他已经通过各种修复减少了他遇到的错误数量,包括重新创建用户、使用 SCRAM-SHA-1 和增加服务器上允许的最大连接数。进行这些更改后,我仍然会遇到此问题。
我猜这个问题与与数据库身份验证结合使用时的线程有关。出于明显的原因,我不能通过禁用身份验证来解决该问题,将此代码投入生产使用,同样,使用同步模型而不是异步模型似乎适得其反。我可以采取哪些步骤来尝试解决身份验证问题?这可能是 Mongo C# 驱动程序中的错误,还是我只是用错了它。
如果能提供有关我接下来可以尝试的方法或替代方法的见解,我们将不胜感激。
编辑:要求的最小可重现示例:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
namespace MongoDbIssueExample
{
internal class Program
{
private static IServiceProvider services;
private static IServiceProvider BuildDependencyInjector()
{
services = new ServiceCollection()
.AddSingleton<TestThingsService>()
.AddSingleton<IMongoClient>(factory => new MongoClient("mongodb://username:password@server:27017"))
.BuildServiceProvider();
return services;
}
private static async Task DoSeed()
{
var service = services.GetService<TestThingsService>();
// Don't do these async as we'll never get any data in...
service.CreateTestThings().Wait();
service.CreateOtherTestThings().Wait();
}
private static async Task DoTest()
{
var service = services.GetService<TestThingsService>();
var things = service.GetTestThings();
var otherThings = service.GetOtherTestThings();
Task.WaitAll(things, otherThings);
}
private static async Task Main(string[] args)
{
BuildDependencyInjector();
await DoTest();
}
}
public class TestThingsService
{
private readonly IMongoClient _client;
private readonly IMongoDatabase _database;
private readonly IMongoCollection<OtherTestThing> _otherTestThingsCollection;
private readonly IMongoCollection<TestThing> _testThingsCollection;
public TestThingsService(IMongoClient client)
{
_client = client;
_database = _client.GetDatabase("Things");
_testThingsCollection = _database.GetCollection<TestThing>("TestThings");
_otherTestThingsCollection = _database.GetCollection<OtherTestThing>("OtherTestThings");
}
public async Task CreateOtherTestThings()
{
for (var item = 1; item <= 10000; item++)
{
var testThing = new OtherTestThing {Id = item, Name = $"Other thing no. {item}", WhenCreated = DateTime.UtcNow};
await _otherTestThingsCollection.ReplaceOneAsync(f => f.Id == item, testThing, new ReplaceOptions {IsUpsert = true});
}
}
public async Task CreateTestThings()
{
for (var item = 1; item <= 10000; item++)
{
var testThing = new TestThing {Id = item, Name = $"Thing no. {item}", WhenCreated = DateTime.UtcNow};
await _testThingsCollection.ReplaceOneAsync(f => f.Id == item, testThing, new ReplaceOptions {IsUpsert = true});
}
}
public async Task<List<OtherTestThing>> GetOtherTestThings()
{
return await _otherTestThingsCollection.Find(_ => true).ToListAsync();
}
public async Task<List<TestThing>> GetTestThings()
{
return await _testThingsCollection.Find(_ => true).ToListAsync();
}
}
public class OtherTestThing
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime WhenCreated { get; set; }
}
public class TestThing
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime WhenCreated { get; set; }
}
}
需要参考资料如下:
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
<PackageReference Include="MongoDB.Bson" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.11.0" />
最小密码为:
var client = new MongoClient(_connection_string);;
var database = client.GetDatabase(Database_name);
var collection = database.GetCollection<T>(Collection_name);
var document_count = collection.EstimatedDocumentCount();
删除最后一行,它工作正常。
environment: .Net Core SDK 3.1.401, Mongodb.Driver 2.11.0, using Sharding Mongodb 4.4.
我最近也遇到了这个问题,并且能够使用您的测试代码在本地重现该问题。我调试了 mongo c# 驱动程序,发现问题是一个竞争条件,使用的功能旨在将身份验证协议的一部分搭载到初始服务器发现 (isMaster) 请求上——该身份验证状态的一部分最终被存储在全局身份验证器对象中,多个并发身份验证请求使用最后看到的状态,这意味着一个请求看到为另一个请求生成的随机数与服务器响应不匹配,导致抛出异常。
通过将身份验证状态与发现请求相关联,存在 mongo c# 驱动程序的 more recent bug report in the mongo bug tracker which contains the details. I have also fixed the issue in this fork。我正在使用分叉 mongo 驱动程序的构建来解决此问题(您可以通过分叉存储库中的 运行 build --target=Package 创建其中一个)。
此问题已在更新版本的 MongoDB C# 驱动程序版本 2.11.2 中得到解决。
我最近向我的开发数据库添加了身份验证,针对“admin”数据库进行身份验证,并在我的连接字符串中使用 username/password 组合,例如mongodb://username:password@server:27017
。几乎立即我开始看到连接无法打开,异常显示“服务器发送了一个无效的随机数”。为了尝试缓解这个问题,我查看了我的 IMongoClient 对象的生命周期,并从实例化许多此类对象转变为使用使用 Microsoft.Extensions.DependencyInjection 库注入到我的业务服务中的 Singleton。这并没有缓解问题。我使用 .AddSingleton<IMongoClient>(factory => new MongoClient(Configuration["Storage:MongoConnectionString"]))
在 Startup.cs 中设置了 MongoClient。我知道连接字符串是正确的,因为它在 MongoDB Compass 中工作,也因为通过驱动程序的前几次调用成功;当多个并发调用正在进行时,问题开始出现。
我在 .NET Core 3.1.2 下使用 MongoDB .NET 驱动程序,版本 2.11.0。问题出现在我的本地环境 运行 Windows 10,以及我在 VMware Photon 上 Docker 内的暂存环境 运行。
应用程序有两个组件与 MongoDB 建立连接,它们都是 ASP.Net 核心应用程序,其中一个服务于 API 用于我的应用程序的交互式使用,和一个 运行 用于后台处理的 Quartz 调度程序。我是 运行 MongoDB 4.4.0 社区,位于 Docker 容器中。
我对包括驱动程序的引用是:
<PackageReference Include="MongoDB.Bson" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.11.0" />
根据 this post on the MongoDB Jira site 的说法,我不是第一个遇到这个问题的人。 Mathias Lorenzen 在 Jira 上的问题中建议,他已经通过各种修复减少了他遇到的错误数量,包括重新创建用户、使用 SCRAM-SHA-1 和增加服务器上允许的最大连接数。进行这些更改后,我仍然会遇到此问题。
我猜这个问题与与数据库身份验证结合使用时的线程有关。出于明显的原因,我不能通过禁用身份验证来解决该问题,将此代码投入生产使用,同样,使用同步模型而不是异步模型似乎适得其反。我可以采取哪些步骤来尝试解决身份验证问题?这可能是 Mongo C# 驱动程序中的错误,还是我只是用错了它。
如果能提供有关我接下来可以尝试的方法或替代方法的见解,我们将不胜感激。
编辑:要求的最小可重现示例:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
namespace MongoDbIssueExample
{
internal class Program
{
private static IServiceProvider services;
private static IServiceProvider BuildDependencyInjector()
{
services = new ServiceCollection()
.AddSingleton<TestThingsService>()
.AddSingleton<IMongoClient>(factory => new MongoClient("mongodb://username:password@server:27017"))
.BuildServiceProvider();
return services;
}
private static async Task DoSeed()
{
var service = services.GetService<TestThingsService>();
// Don't do these async as we'll never get any data in...
service.CreateTestThings().Wait();
service.CreateOtherTestThings().Wait();
}
private static async Task DoTest()
{
var service = services.GetService<TestThingsService>();
var things = service.GetTestThings();
var otherThings = service.GetOtherTestThings();
Task.WaitAll(things, otherThings);
}
private static async Task Main(string[] args)
{
BuildDependencyInjector();
await DoTest();
}
}
public class TestThingsService
{
private readonly IMongoClient _client;
private readonly IMongoDatabase _database;
private readonly IMongoCollection<OtherTestThing> _otherTestThingsCollection;
private readonly IMongoCollection<TestThing> _testThingsCollection;
public TestThingsService(IMongoClient client)
{
_client = client;
_database = _client.GetDatabase("Things");
_testThingsCollection = _database.GetCollection<TestThing>("TestThings");
_otherTestThingsCollection = _database.GetCollection<OtherTestThing>("OtherTestThings");
}
public async Task CreateOtherTestThings()
{
for (var item = 1; item <= 10000; item++)
{
var testThing = new OtherTestThing {Id = item, Name = $"Other thing no. {item}", WhenCreated = DateTime.UtcNow};
await _otherTestThingsCollection.ReplaceOneAsync(f => f.Id == item, testThing, new ReplaceOptions {IsUpsert = true});
}
}
public async Task CreateTestThings()
{
for (var item = 1; item <= 10000; item++)
{
var testThing = new TestThing {Id = item, Name = $"Thing no. {item}", WhenCreated = DateTime.UtcNow};
await _testThingsCollection.ReplaceOneAsync(f => f.Id == item, testThing, new ReplaceOptions {IsUpsert = true});
}
}
public async Task<List<OtherTestThing>> GetOtherTestThings()
{
return await _otherTestThingsCollection.Find(_ => true).ToListAsync();
}
public async Task<List<TestThing>> GetTestThings()
{
return await _testThingsCollection.Find(_ => true).ToListAsync();
}
}
public class OtherTestThing
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime WhenCreated { get; set; }
}
public class TestThing
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime WhenCreated { get; set; }
}
}
需要参考资料如下:
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
<PackageReference Include="MongoDB.Bson" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.11.0" />
最小密码为:
var client = new MongoClient(_connection_string);;
var database = client.GetDatabase(Database_name);
var collection = database.GetCollection<T>(Collection_name);
var document_count = collection.EstimatedDocumentCount();
删除最后一行,它工作正常。
environment: .Net Core SDK 3.1.401, Mongodb.Driver 2.11.0, using Sharding Mongodb 4.4.
我最近也遇到了这个问题,并且能够使用您的测试代码在本地重现该问题。我调试了 mongo c# 驱动程序,发现问题是一个竞争条件,使用的功能旨在将身份验证协议的一部分搭载到初始服务器发现 (isMaster) 请求上——该身份验证状态的一部分最终被存储在全局身份验证器对象中,多个并发身份验证请求使用最后看到的状态,这意味着一个请求看到为另一个请求生成的随机数与服务器响应不匹配,导致抛出异常。
通过将身份验证状态与发现请求相关联,存在 mongo c# 驱动程序的 more recent bug report in the mongo bug tracker which contains the details. I have also fixed the issue in this fork。我正在使用分叉 mongo 驱动程序的构建来解决此问题(您可以通过分叉存储库中的 运行 build --target=Package 创建其中一个)。
此问题已在更新版本的 MongoDB C# 驱动程序版本 2.11.2 中得到解决。