【.NET】| 作者/Mike Rousos
本文来自Microsoft Docs官方文档,提供了ASP.NET Core性能最佳做法的准则。
1充分利用缓存
缓存在本文档的多个部分中进行了讨论。有关详细信息,请参阅 ASP.NET Core 中的响应缓存。
参考:https://docs.microsoft.com/zh-cn/aspnet/core/performance/performance-best-practices?view=aspnetcore-6.0
2了解热代码路径
在本文档中,热代码路径定义为经常调用并形成大量执行时间的代码路径。热代码路径通常会限制应用横向扩展和性能,在本文档的多个部分中进行了讨论。
3避免阻塞调用
ASP.NET Core 应用应设计为可同时处理许多请求。异步 API 允许较小线程池处理数千个并发请求,无需等待阻塞调用。线程可以处理另一个请求,而不是等待长时间运行的同步任务完成。
ASP.NET Core 应用中的一个常见性能问题是阻塞可以异步进行的调用。许多同步阻塞调用都会导致线程池饥饿和响应时间降低。
禁止行为:
- 通过调用 Task.Wait 或 Task<TResult>.Result 阻止异步执行。
- 获取常见代码路径中的锁。当构建为并行运行代码时,ASP.NET Core 应用的性能最高。
- 调用 Task.Run 并立即等待。ASP.NET Core 已经在普通线程池线程上运行应用代码,因此调用 Task.Run 只会导致不必要的额外线程池计划。即使计划的代码会阻止某个线程,Task.Run 也不会阻止该线程。
建议做法:
- 使热代码路径成为异步。
- 如果有异步 API 可用,则异步调用数据访问、I/O 和长时间运行的操作 API。 不要使用 来异步同步 API。
- 使控制器/Razor Page 操作成为异步。为了获益于 async/await 模式,整个调用堆栈都是异步的。
探查器(例如 PerfView)可用于查找频繁添加到线程池中的线程。 Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start
事件指示添加到线程池的线程。
4跨多个较小页面返回大集合
网页不应一次加载大量数据。返回对象集合时,请考虑它是否会导致性能问题。确定设计是否可能会产生以下不良结果:
- OutOfMemoryException 或占用大量内存
- 线程池资源不足(请参阅以下有关 IAsyncEnumerable<T> 的注解)
- 响应时间缓慢
- 频繁的垃圾回收
请添加分页以缓解以上情形。使用页面大小和页面索引参数时,开发人员应支持返回部分结果的设计。当需要详尽结果时,应使用分页来异步填充结果批次,以避免锁定服务器资源。
有关分页和限制返回的记录数的详细信息,请参阅:
- 性能注意事项
- 将分页添加到 ASP.NET Core 应用
5返回IEnumerable<T>或IAsyncEnumerable<T>
从操作返回 IEnumerable<T>
会导致序列化程序同步集合迭代。因此会阻止调用,并且可能会导致线程池资源不足。若要避免同步枚举,请在返回可枚举内容前使用 ToListAsync
。
从 ASP.NET Core 3.0 开始,IAsyncEnumerable<T>
可用作异步枚举的 IEnumerable<T>
的替代方法。有关详细信息,请参阅控制器操作返回类型。
6最大程度减少大型对象分配
.NET Core 垃圾回收器在 ASP.NET Core 应用中自动管理内存分配和释放。自动垃圾回收通常意味着开发人员无需担心如何或何时释放内存。但是,清理未引用的对象会占用 CPU 时间,因此开发人员应最大限度减少热代码路径中的对象分配。垃圾回收在大型对象(> 85 K 字节)上成本特别高昂。大型对象存储在大型对象堆上,需要完整(第 2 代)垃圾回收才能清理。与第 0 代和第 1 代回收不同,第 2 代回收需要临时暂停应用执行。频繁分配和取消分配大型对象可能会导致性能不一致。
建议:
- 请考虑缓存经常使用的大型对象。缓存大型对象会阻止进行成本高昂的分配。
- 使用 存储大型数组来池缓冲区。
- 请勿在热代码路径上分配许多生存期较短的大型对象。
可以通过在 PerfView 中查看垃圾回收 (GC) 统计信息并检查以下内容来诊断内存问题(如前面的问题):
- 垃圾回收暂停时间。
- 花费在垃圾回收上的处理器时间百分比。
- 第 0 代、第 1 代和第 2 代的垃圾回收量。
有关详细信息,请参阅垃圾回收和性能。
参考:https://docs.microsoft.com/zh-CN/dotnet/standard/garbage-collection/performance
7优化数据访问和I/O
与数据存储和其他远程服务的交互通常是 ASP.NET Core 应用的最慢部分。高效读取和写入数据对于良好的性能至关重要。
建议:
- 请异步调用所有数据访问 API。
- 请勿检索不需要的数据。编写查询以便仅返回当前 HTTP 请求所需的数据。
- 如果可接受稍微过时的数据,请考虑缓存从数据库或远程服务检索的经常访问的数据。根据方案使用 MemoryCache 或 DistributedCache。有关详细信息,请参阅 ASP.NET Core 中的响应缓存。
- 请尽量缩短网络往返。目标是在单个调用而不是多个调用中检索所需数据。
- 当出于只读目的访问数据时,请在Entity Framework Core中使用无跟踪查询。EF Core可以更有效地返回无跟踪查询的结果。
- 请筛选和聚合 LINQ 查询(例如使用 、
.Select
或.Sum
语句),以便数据库执行筛选。 - 请考虑 EF Core 会在客户端上解析一些查询运算符,这可能会导致查询执行效率低下。有关详细信息,请参阅客户端评估性能问题。
- 请勿对集合使用投影查询,这可能会导致执行“N 1”个 SQL 查询。有关详细信息,请参阅相关子查询优化。
请参阅 EF 高性能,以了解可提高大规模应用性能的方法:
- DbContext 池
- 显式编译的查询
建议在提交基本代码之前衡量前面高性能方法的影响。已编译查询的额外复杂性可能无法证明性能改进的合理性。
通过使用 Application Insights 或分析工具查看访问数据所用的时间,可以检测到查询问题。大多数数据库还提供有关频繁执行的查询的统计信息。
8与HttpClientFactory之间的池HTTP连接
虽然 HttpClient 实现了 IDisposable
接口,但它是为重复使用而设计的。关闭的 HttpClient
实例使套接字在短时间内以 TIME_WAIT
状态保持打开。如果经常使用创建和释放 HttpClient
对象的代码路径,则应用可能会耗尽可用的套接字。在 ASP.NET Core 2.1 中引入了 HttpClientFactory,以作为此问题的解决方案。它会处理池 HTTP 连接以优化性能和可靠性。
建议:
- 请勿直接创建和释放 实例。
- 请勿使用 HttpClientFactory 检索 实例。有关详细信息,请参阅使用 HttpClientFactory 实现可复原的 HTTP 请求。
9使常用代码路径保持快速
你希望所有代码都可快速执行。经常调用的代码路径是优化的关键。其中包括:
- 应用请求处理管道中的中间件组件,尤其是在管道中早期运行的中间件。这些组件对性能具有很大影响。
- 对每个请求都执行或是按请求执行多次的代码。例如,自定义日志记录、授权处理程序或暂时性服务的初始化。
建议:
- 请勿将自定义中间件组件用于长时间运行的任务。
- 请使用性能分析工具(例如 Visual Studio 诊断工具或 PerfView)标识热代码路径。
10在HTTP请求外部完成长时间运行任务
对 ASP.NET Core 应用进行的大多数请求可以由调用必要服务并返回 HTTP 响应的控制器或页面模型进行处理。对于涉及长时间运行的任务的一些请求,最好使整个请求-响应过程异步进行。
建议:
- 在普通 HTTP 请求处理过程中,请勿等待长时间运行的任务完成。
- 请考虑使用后台服务处理长时间运行的请求,或使用 Azure 函数进行进程外处理。在进程外完成工作对于 CPU 密集型任务尤其有利。
- 请使用实时通信选项(如 )以异步方式与客户端通信。
11缩小客户端资产
具有复杂前端的 ASP.NET Core 应用会经常处理许多 JavaScript、CSS 或图像文件。初始加载请求的性能可以通过以下方式得到提高:
- 捆绑,即将多个文件合并为一个文件。
- 缩小,即通过删除空格和注释来减小文件的大小。
建议:
- 请使用捆绑和缩小准则,其中提及了兼容工具,并演示如何使用 ASP.NET Core 的 标记处理 和 Production 环境。
- 请考虑使用其他第三方工具(如 Webpack)进行复杂客户端资产管理。
12压缩响应
减小响应大小通常可显著提高应用的响应速度。减小有效负载大小的一种方式是压缩应用的响应。有关详细信息,请参阅响应压缩。
参考:https://docs.microsoft.com/zh-cn/aspnet/core/performance/response-compression?view=aspnetcore-6.0
13使用最新ASP.NET Core版本
每个新版本的 ASP.NET Core 都包含性能改进。.NET Core 和 ASP.NET Core 中的优化意味着较新版本的性能通常优于较旧版本。例如,.NET Core 2.1 添加了对已编译正则表达式的支持,可受益于 SpanT>。ASP.NET Core 2.2 添加了对 HTTP/2 的支持。 ASP.NET Core 3.0 添加了许多改进,可减少内存使用量并提高吞吐量。如果性能是优先事项,请考虑升级到当前版本的 ASP.NET Core。
14尽量减少异常
异常应很少出现。相对于其他代码流模式,引发和捕获异常的速度较慢。因此,不应使用异常来控制正常程序流。
建议:
- 请勿将引发或捕获异常用作正常程序流的一种方法(尤其是在热代码路径中)。
- 请在应用中包含逻辑,以检测和处理会导致异常的状况。
- 对于不寻常或意外状况,请引发或捕获异常。
应用诊断工具(如 Application Insights)可帮助识别应用中可能会影响性能的常见异常。