ASP.NET Core 性能最佳实践(一)

这篇文章的主要内容来源于.NET文档,此处翻译前4条内容,其他内容会陆续贴出来

  1. 积极使用缓存
  2. 明确”热代码路径”
  3. 避免使用阻塞调用
  4. 返回值使用IEnumerable<T> 还是 IAsyncEnumerable<T>?

 

积极使用缓存

详情请查看:ASP.NET Core 中的响应缓存.

asp.net core 中的几类缓存:

1.响应缓存(输出缓存) ResponseCache

响应缓存本质上是控制响应数据Header头中的Cache-Control,从而设置浏览器对Http请求的缓存。

有两种实现方式:

1.使用响应缓存特性:ResponseCacheAttribute

  下面的示例表示响应缓存60秒

[ResponseCache(Duration = 60)]

  下面的示例表示不使用响应缓存,即响应Header头为:Cache-Control:no-store,no-cache

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]

 

2.使用响应缓存中间件 (可全局设定缓存策略)

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddResponseCaching();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
app.UseResponseCaching(); app.Use(async (context, next) => { context.Response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue() { Public = true, MaxAge = TimeSpan.FromSeconds(10) }; context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] = new string[] { "Accept-Encoding" }; await next(); }); }

注意:如果跨域中间件和响应缓存中间件共同使用的话,那么跨域中间件要在前面。

使用响应缓存设置缓存策略,不仅可以告知客户端缓存响应数据,也可以明确告知客户端不要缓存数据(Cache-Control:no-store,no-cache)。

 

2.内存缓存 IMemoryCache

在Startup中注入内存缓存依赖

public class Startup
{
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache(option =>
            {
                option.CompactionPercentage = 0.1;
                option.SizeLimit = 1000;
            });
        }
}

MemoryCacheOptions的解释:

SizeLimit:缓存的最大大小。

CompactionPercentage:表示超出缓存最大大小时,需要压缩的缓存比例。

ExpirationScanFrequence:表示扫描过期项的时间间隔。(注意:只有在访问缓存之后才会触发)

使用内存缓存

public class HomeController : Controller
{
        private readonly IMemoryCache _cache;

        public HomeController(IMemoryCache cache)
        {
            _cache = cache;
        }

        public IActionResult Index()
        {
            _cache.TryGetValue("Test", out object value);
            if (value == null)
            {
                value = "Hello";
                _cache.Set("Test", value);
            }
            this.ViewBag.Test = value;

            return View();
        }
}

注意事项:缓存到达失效时间,并不会被立即处理,因为并没有对缓存失效进行判断的定时器。

第一种情况:当缓存再次被访问( (GetSetRemove))时,会触发失效扫描。

第二种情况:使用CancellationTokenSource,当CancellationTokenSource失效时,自动触发缓存的失效。

 

3.分布式缓存 IDistributedCache

.NET Core中实现了IDistributedCache接口的四个分布式缓存:

1.分布式内存缓存

内存缓存,本质上并不是分布式缓存,主要用于在开发,测试阶段使用。

services.AddDistributedMemoryCache();

2.分布式SQLServer缓存

使用SQLServer数据库作为数据存储,用以提供kv存储的分布式服务

services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = _config["DistCache_ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

3.分布式缓存Redis

首先安装程序包:Microsoft.Extensions.Caching.StackExchangeRedis

public void ConfigureServices(IServiceCollection services)
{
    services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = "localhost:6379";
    });
}

 

4.分布式缓存NCache

NCache是一个为.NET应该程序开发的,非常快,非常稳定的,开放源代码的分布式缓存。(GitHub上的解释)

 

4.对象池 ObjectPool

 

明确应用的”热代码路径”

”热代码路径”的定义为访问频繁并且耗时较长的代码。”热代码”会限制应用对水平扩展,并且对性能影响很明细,这个问题在文档的后续部分也会多次提及。

 我认为叫“关键路径”更贴切,这一点的本质是找出系统中的性能瓶颈,确定对性能影响最大的代码,然后加以解决。

 

避免使用阻塞调用

ASP.NET Core 程序应该被设计成同时处理多个请求。异步API使用一个小线程池可以处理上千个并发请求,而不会阻塞。这样请求线程可以去处理其他请求,而不是等待一个长时同步任务完成。

ASP.NET Core 程序的一个常见的性能问题是,阻塞了本该异步执行的调用。很多同步调用会导致线程池饥饿,增大相应时间。

不要像下面这样做:

  • 通过 Task.Wait 或 Task.Result 阻塞异步执行。
  • 在常用代码上使用锁(lock)。ASP.NET Core 程序被设计为并行架构时执行效率最高。
  • 自己定义一个 Task.Run ,然后去await 。由于ASP.NET Core 程序已经是在通用线程池上运行了,调用 Task.Run 是没有必要的,

应该这样做:

  • 异步调用“热代码”。
  • 在访问数据,I/O,长时操作的时候,如果有异步API就尽量使用异步API。不要使用Task.Run将同步API异步执行。
  • 使Controller或Razor Page中的Action异步化。将整个调用栈异步化,以便使用 async/await。

性能分析器 PerfView,可以用来查找频繁加入线程池的线程。

Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start 表明一个线程加入了线程池。

 

返回IEnumerable<T> 还是 IAsyncEnumerable<T>

如果Action Result返回IEnumerable<T>,那么序列化器以同步的方式处理集合迭代,这样阻塞调用可能会导致线程池饥饿,为避免同步迭代,可在返回迭代之前调用 ToListAsync()。

从ASP.NET Core 3.0开始,IAsyncEnumerable<T>作为异步枚举,可取代IEnumerable<T>。

 

posted @ 2020-09-18 19:52  北京刘先生  阅读(1037)  评论(0编辑  收藏  举报