如何预热Web API,减少初次执行时间

前言

  • 在上次的《差距50倍!为什么Web API第一次执行这么慢?》文章中,我们发现了部分耗时比较大的方法:

Microsoft.AspNetCore.Mvc.Infrastructure.ActionInvokerFactory.CreateInvoker - 30.15ms

  • 查看源代码,ActionInvokerFactory使用的是Singleton生命周期:
  1. services.TryAddSingleton<IActionInvokerFactory, ActionInvokerFactory>();
  2. 而在构造函数中,会执行排序方法:
  3. public ActionInvokerFactory(IEnumerable<IActionInvokerProvider> actionInvokerProviders)
  4. {
  5. _actionInvokerProviders = actionInvokerProviders.OrderBy(item => item.Order).ToArray();
  6. }
  • 我想,这应该是初次执行时间较长的部分原因。

思路

  • 使用Singleton生命周期的类,在应用的生存期内仅创建一次,相当于静态类。

  • 如果我们在执行Web API之前,就使用过了ActionInvokerFactory,那么在第一次执行Web API时就不会再次初始化它,应该可以减少初次执行时间。

验证

  • 创建一个WarmController,代码非常简单:
  1. [ApiController]
  2. [Route("[controller]")]
  3. public class WarmController : ControllerBase
  4. {
  5. [HttpGet]
  6. public string Get()
  7. {
  8. return "OK";
  9. }
  10. }
  • 首先访问Warm接口,再访问WeatherForecast接口,发现初次访问WeatherForecast的执行时间确实大幅减少:
未预热 预热后
初次执行时间 100ms 21ms

实现

  • 但是,不太好每次启动服务后,都先手工访问一下预热接口。
  • 我们可以设置成,在启动后,自动访问一下预热接口。
  • 利用应用程序生存期事件,可以达到这一目的。
  1. 修改Startup.cs,代码如下:
  2. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
  3. {
  4. ...
  5. lifetime.ApplicationStarted.Register(OnAppStarted);
  6. }
  7. public void OnAppStarted()
  8. {
  9. var url = Configuration[WebHostDefaults.ServerUrlsKey];
  10. var warm = url.Split(';')[0] + "/warm";
  11. new HttpClient().GetAsync(warm).Wait();
  12. }
  • 启动后访问WeatherForecast接口,发现确实已经预热过了。

结论

在本文中,我们利用了ASP.NET Core应用程序生存期事件,实现了Web API预热。