【问题标题】:Why does response time go up when the number of concurrent requests per second to my Asp.Net Core API goes up当我的 Asp.Net Core API 的每秒并发请求数增加时,为什么响应时间会增加
【发布时间】:2019-09-02 07:18:13
【问题描述】:

我正在测试负载下的端点。对于每秒 1 个请求,平均响应时间约为 200 毫秒。端点会执行一些非常快的数据库查找(全部读取),并且始终是异步的。

但是,当每秒处理数百个请求 (req/sec) 时,平均响应时间会上升到一秒以上。

我在以下位置查看了最佳实践指南:

https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-2.2

避免阻塞调用”和“尽量减少大对象分配”之类的一些建议似乎并不适用,因为我已经在整个过程中使用异步并且我的响应单个请求的大小小于 50 KB。

有一些看起来可能有用,例如:

https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#high-performance https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-2.2#pool-http-connections-with-httpclientfactory

问题:

  1. 为什么平均响应时间会随着请求/秒的增加而增加?
  2. 上面我标记为“可能有用”的建议是否可能会有所帮助?我之所以问,是因为虽然我想尝试所有方法,但很遗憾我的时间有限,所以我想先尝试最有可能提供帮助的选项。
  3. 还有其他值得考虑的选择吗?

我已经查看了这两个现有线程,但都没有回答我的问题:

Correlation between requests per second and response time?

ASP.NET Web API - Handle more requests per second

【问题讨论】:

  • 如果所有的数据库查询都很快,为什么低负载下要200ms才能响应?他们有多快?除非您的响应时间主要受网络延迟的影响,否则您会期望完成更多工作需要更长的时间。您的连接池如何设置数据库?
  • @jjanes 我怀疑响应时间涉及网络延迟。在 localhost 上,端点会在 20 毫秒内做出响应。但是什么是快呢?即使实际的端点确实需要 200 毫秒,当然在并发的情况下,它不应该使响应时间增加 5 倍,增加请求/秒应该是吗?也许我错了,但这不是并发的全部意义吗?
  • 我不确定连接池,我需要回复你。

标签: postgresql entity-framework asp.net-core


【解决方案1】:

如果不访问代码,将很难回答您的具体问题,但要考虑的主要事项是 EF 生成的数据库查询的大小和复杂性。使用 async/await 将提高 Web 服务器启动请求的响应能力,但负载下的请求处理时间将在很大程度上取决于数据库成为争用点时正在运行的查询。您将希望确保所有查询都尽可能简约。例如,以下 3 种陈述之间存在巨大差异:

var someData = context.SomeTable.Include(x => x.SomeOtherTable)
    .ToList()
    .Where(x => x.SomeCriteriaMethod())
    .ToList();

var someData = context.SomeTable.Include(x => x.SomeOtherTable)
    .Where(x => x.SomeField == someField && x.SomeOtherTable.SomeOtherField == someOtherField)
    .ToList();

var someData = context.SomeTable
    .Where(x => x.SomeField == someField && x.SomeOtherTable.SomeOtherField == someOtherField)
    .Select(x => new SomeViewModel 
    {
       SomeTableId = x.SomeTableId,
       SomeField = x.SomeField,
       SomeOtherField = x.SomeOtherTable.SomeOtherField
    }).ToList();

像上面第一个这样的示例效率极低,因为它们最终会在过滤行之前从数据库中加载相关表中的所有数据。尽管您的 Web 服务器可能只传回几行,但它已经从数据库中请求了所有内容。当开发人员面临他们想要过滤 EF 无法转换为 SQL 的值(例如函数)的场景时,这些类型的场景会蔓延到应用程序中,因此他们通过调用 ToList 来解决它,或者它可以作为分离不良的副产品,例如返回 IEnumerable 的存储库模式。

第二个例子稍微好一点,他们避免使用全读 ToList() 调用,但调用仍然会为不需要的数据加载整行。这会占用数据库和 Web 服务器上的资源。

第三个示例演示了精炼查询以仅返回消费者需要的绝对最少的数据。这样可以更好地利用数据库服务器上的索引和执行计划。

在负载下您可能面临的其他性能缺陷是延迟加载等问题。数据库将执行有限数量的并发请求,因此如果某些查询启动了额外的延迟加载请求,那么当没有加载时,这些请求会立即执行。但在负载下,它们与其他查询和延迟加载请求一起排队,这可能会限制数据拉取。

最终,您应该针对您的数据库运行 SQL 分析器,以捕获正在执行的 SQL 查询的种类和数量。在测试环境中执行时,请密切注意读取计数和 CPU 成本,而不是总执行时间。作为一般经验法则,更高的读取和 CPU 成本查询将更容易受到负载下执行时间爆裂的影响。它们需要更多资源来运行,并且“接触”更多行意味着更多等待行/表锁。

另一件需要注意的事情是超大型数据系统中的“繁重”查询,这些查询需要涉及很多行,例如报表,在某些情况下,还需要高度可定制的搜索查询。如果需要这些,您应该考虑规划您的数据库设计,以包括一个只读副本以运行报告或大型搜索表达式,以避免主数据库中的行锁定情况会降低典型读写查询的响应能力。

编辑:识别延迟加载查询。

这些显示在分析器中,您可以在其中查询顶级表,但随后会看到一些针对相关表的附加查询。

例如,假设您有一个名为 Order 的表,其中有一个名为 Product 的相关表、另一个名为 Customer 的表和另一个名为 Address 的交货地址。要读取某个日期范围内的所有订单,您会看到如下查询:

SELECT [OrderId], [Quantity], [OrderDate] [ProductId], [CustomerId], [DeliveryAddressId] FROM [dbo].[Orders] WHERE [OrderDate] >= '2019-01-01' AND [OrderDate] < '2020-01-01'

您只想加载订单并返回它们。

当序列化程序遍历字段时,它会找到引用的产品、客户和地址,并通过尝试读取这些字段,将导致延迟加载:

SELECT [CustomerId], [Name] FROM [dbo].[Customers] WHERE [CustomerId] = 22
SELECT [ProductId], [Name], [Price] FROM [dbo].[Products] WHERE [ProductId] = 1023
SELECT [AddressId], [StreetNumber], [City], [State], [PostCode] FROM [dbo].[Addresses] WHERE [AddressId] = 1211

如果您的原始查询返回 100 个订单,您可能会看到上述查询集的 100 倍,每个订单的一组作为延迟加载命中 1 个订单行将尝试按客户 ID 查找相关客户,相关产品 ID 和相关地址(交货地址 ID)。这可以而且确实代价高昂。在测试环境中运行时它可能不可见,但这会增加很多潜在的查询。

如果使用 .Include() 为相关实体进行预先加载,EF 将编写 JOIN 语句以一次性获取所有相关行,这比获取每个单独的相关实体要快得多。尽管如此,这可能会导致提取大量您不需要的数据。避免这种额外成本的最佳方法是利用通过Select 的投影来仅检索您需要的列。

【讨论】:

  • 感谢史蒂夫的建议,不胜感激。很抱歉没有显示实际代码,但我相当有信心,尽管瓶颈不是数据库请求。被测端点使 EF 调用两个单独的表以从每个表中选择所有行。没有连接,没有过滤,只是全选。两个表都有大约 3-4 列(主要是 guid),可能总共有几百行。
  • 当我在我的本地主机上测试这个端点时,它需要不到 20 毫秒。我也没有看到 COU 上有任何负载。
  • 这些表前面的实体只有 GUID 字段,如果这些 GUID 是 FK,则没有其他实体的导航属性?如果实体是从服务调用传回的,则序列化程序将“触摸”导航属性,从而导致它们被延迟加载。我建议对数据库运行分析器以观察正在执行的查询以消除数据库作为瓶颈。除此之外,随着基于可用内核和上下文切换的并发请求数量的增加,每个请求的处理能力会降低。
  • 谢谢。表中的向导确实是 FK。如果没有显式打开延迟加载,我的印象是导航属性不会延迟加载?还是不是这样?
  • 如果是 EF6,默认开启延迟加载。分析器将确认延迟加载是否是一个因素。使用Select 将实体投影到视图模型以获取 FK 字段消除了任何延迟加载命中、导航属性或否。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 2018-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多