ASP.NET Web API 2 中的并发请求比单个请求慢近六倍
Concurrent request in ASP.NET Web API 2 is nearly six times slower than a single request
背景:
我们构建了一个与 ASP.NET Web API 2 后端通信的 React SPA,.NET Framework 4.6.1
。在我们的初始加载中,我们发出两个单独的请求来加载数据。当加载大量数据时,我们注意到 API 请求在应用程序中发出时比我们在 Postman 中单独尝试请求时要慢得多。
原文:
示例结构,fetch当然使用我们的API:
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});
fetch('http://example.com/otherMovies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});
示例 C# API 方法:
[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult GetRenewalCycleForCustomer(string bsid, int caseId)
{
var customerNumbers = GetCustomerNumbers();
var userId = HttpContext.Current.User.Identity.GetUserId<int>();
var user = identityDb.Users.Find(userId);
var customerNumbers = user.ApplicationUserCustomerNumbers.Select(x => new CustomerNumberKey() { bsid = x.CustomerNumber.bsid, NameNo = x.CustomerNumber.NameNo }).ToList();
var db = new DbContext();
var caseService = new CaseService(db);
var portfolioTabViewModel = caseService.GetPortfolioTabViewModelForCustomer(bsid, caseId, customerNumbers);
return Ok(portfolioTabViewModel);
}
OS is Windows 10 Pro 和IIS 应该可以处理10 个并发连接,根据Internet。无论如何都没有关系,因为我们将它作为 Windows 服务器托管在 Azure 上,并且我们在那里有相同的响应时间。也尝试了 App Service,结果是一样的。
与 Postman Runner 同步测试响应时间
Postman Runner 的两个实例同时运行:
其他说明:
似乎与硬件无关,因为 CPU,无论是否同时发出请求,内存和磁盘都不会受到值得注意的影响。
我们能做些什么来解决这个问题?
似乎 运行 并行的非异步方法:
一些线程建议会话状态,但我在 Web.config
中找不到任何对此的引用,这不是我启用的任何内容。搜索 HttpContext.Current.SetSessionStateBehavior
得到 0 个结果。
Windows 10 个资源:
https://serverfault.com/a/800518/293367
https://forums.asp.net/t/2100558.aspx?concurrent+connections+on+windows+pro+10+IIS
更新异步和 IIS:
好像跟async和IIS没有关系,用下面的方法测试了并发请求。并发的慢请求似乎取决于其他东西。
异步:
[HttpGet]
[Route("{bsid}/{caseId}")]
public async Task<IHttpActionResult> Get(string bsid, int caseId)
{
await Task.Delay(3000);
return Ok();
}
同步:
[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult Get(string bsid, int caseId)
{
Thread.Sleep(3000);
return Ok();
}
更新 2:具有异步和同步调用的数据库:
好像也不是数据库调用。使用 Include
进行测试,尽管异步调用速度相当慢,但调用速度相似。
异步:
[HttpGet]
[Route("{bsid}/{caseId}/async")]
public async Task<IHttpActionResult> GetAsync(string bsid, int caseId)
{
var db = new DbContext();
var deviations = await db.RenewalCycles.Where(x => x.Deviations.Any())
.Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPName))
.Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPNameType))
.Include(cycle => cycle.TPCase.GoodsAndServicesDescriptions)
.Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCaseRelation))
.Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCountry))
.ToListAsync();
return Ok();
}
同步:
[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult Get(string bsid, int caseId)
{
var db = new DbContext();
var deviations = db.RenewalCycles.Where(x => x.Deviations.Any())
.Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPName))
.Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPNameType))
.Include(cycle => cycle.TPCase.GoodsAndServicesDescriptions)
.Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCaseRelation))
.Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCountry))
.ToList();
return Ok();
}
市长开销的部分竟然是数据库相关的。一个方法使用了db.Cases.Find(bsid, caseId)
,然后将模型转换为视图模型。 Cases
模型反过来有很多关系,所有这些都发出单独的数据库调用,因为模型具有标记为 virtual
的属性,例如 public virtual TPRenewalCycle TPRenewalCycle { get; set; }
以启用延迟加载.通过查看 Visual Studio Output Window (Debug -> Windows -> Output) 并设置 ApplicationDbContext 来找到它,如下所示。
public class ApplicationDbContext : DbContext
{
protected const string ConnectionStringName = "defaultConnection";
public ApplicationDbContext() : base(ConnectionStringName)
{
Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
Database.CommandTimeout = 300;
}
背景:
我们构建了一个与 ASP.NET Web API 2 后端通信的 React SPA,.NET Framework 4.6.1
。在我们的初始加载中,我们发出两个单独的请求来加载数据。当加载大量数据时,我们注意到 API 请求在应用程序中发出时比我们在 Postman 中单独尝试请求时要慢得多。
原文:
示例结构,fetch当然使用我们的API:
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});
fetch('http://example.com/otherMovies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});
示例 C# API 方法:
[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult GetRenewalCycleForCustomer(string bsid, int caseId)
{
var customerNumbers = GetCustomerNumbers();
var userId = HttpContext.Current.User.Identity.GetUserId<int>();
var user = identityDb.Users.Find(userId);
var customerNumbers = user.ApplicationUserCustomerNumbers.Select(x => new CustomerNumberKey() { bsid = x.CustomerNumber.bsid, NameNo = x.CustomerNumber.NameNo }).ToList();
var db = new DbContext();
var caseService = new CaseService(db);
var portfolioTabViewModel = caseService.GetPortfolioTabViewModelForCustomer(bsid, caseId, customerNumbers);
return Ok(portfolioTabViewModel);
}
OS is Windows 10 Pro 和IIS 应该可以处理10 个并发连接,根据Internet。无论如何都没有关系,因为我们将它作为 Windows 服务器托管在 Azure 上,并且我们在那里有相同的响应时间。也尝试了 App Service,结果是一样的。
与 Postman Runner 同步测试响应时间
Postman Runner 的两个实例同时运行:
其他说明:
似乎与硬件无关,因为 CPU,无论是否同时发出请求,内存和磁盘都不会受到值得注意的影响。
我们能做些什么来解决这个问题?
似乎 运行 并行的非异步方法:
一些线程建议会话状态,但我在 Web.config
中找不到任何对此的引用,这不是我启用的任何内容。搜索 HttpContext.Current.SetSessionStateBehavior
得到 0 个结果。
Windows 10 个资源:
https://serverfault.com/a/800518/293367
https://forums.asp.net/t/2100558.aspx?concurrent+connections+on+windows+pro+10+IIS
更新异步和 IIS:
好像跟async和IIS没有关系,用下面的方法测试了并发请求。并发的慢请求似乎取决于其他东西。
异步:
[HttpGet]
[Route("{bsid}/{caseId}")]
public async Task<IHttpActionResult> Get(string bsid, int caseId)
{
await Task.Delay(3000);
return Ok();
}
同步:
[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult Get(string bsid, int caseId)
{
Thread.Sleep(3000);
return Ok();
}
更新 2:具有异步和同步调用的数据库:
好像也不是数据库调用。使用 Include
进行测试,尽管异步调用速度相当慢,但调用速度相似。
异步:
[HttpGet]
[Route("{bsid}/{caseId}/async")]
public async Task<IHttpActionResult> GetAsync(string bsid, int caseId)
{
var db = new DbContext();
var deviations = await db.RenewalCycles.Where(x => x.Deviations.Any())
.Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPName))
.Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPNameType))
.Include(cycle => cycle.TPCase.GoodsAndServicesDescriptions)
.Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCaseRelation))
.Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCountry))
.ToListAsync();
return Ok();
}
同步:
[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult Get(string bsid, int caseId)
{
var db = new DbContext();
var deviations = db.RenewalCycles.Where(x => x.Deviations.Any())
.Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPName))
.Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPNameType))
.Include(cycle => cycle.TPCase.GoodsAndServicesDescriptions)
.Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCaseRelation))
.Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCountry))
.ToList();
return Ok();
}
市长开销的部分竟然是数据库相关的。一个方法使用了db.Cases.Find(bsid, caseId)
,然后将模型转换为视图模型。 Cases
模型反过来有很多关系,所有这些都发出单独的数据库调用,因为模型具有标记为 virtual
的属性,例如 public virtual TPRenewalCycle TPRenewalCycle { get; set; }
以启用延迟加载.通过查看 Visual Studio Output Window (Debug -> Windows -> Output) 并设置 ApplicationDbContext 来找到它,如下所示。
public class ApplicationDbContext : DbContext
{
protected const string ConnectionStringName = "defaultConnection";
public ApplicationDbContext() : base(ConnectionStringName)
{
Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
Database.CommandTimeout = 300;
}