You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.Net Core冷启动/闲置后路由匹配耗时过长求助

问题

我的服务在冷启动(部署完成后)或长时间闲置(如1小时)时响应速度极慢。从日志能看到,路由与控制器动作匹配阶段消耗了大量时间,控制器动作本身的耗时反而不是主要问题。

相关日志:

2023-06-12T09:39:13.855690882Z INFO  [.NET ThreadPool Worker] Microsoft.AspNetCore.Hosting.Diagnostics - Request starting HTTP/1.0 POST xxxx application/json 103
2023-06-12T09:39:14.782777792Z INFO  [.NET ThreadPool Worker] Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker - Route matched with.....
2023-06-12T09:39:16.981415541Z INFO  [.NET ThreadPool Worker] First controller's line 

服务部署在Google云Kubernetes集群的Docker容器中。我尝试用一个每5分钟运行的后台预热服务激活控制器,但没有效果,代码如下:

public class HeatUpService: BackgroundService
{
    private readonly JwtConfiguration _jwtConfiguration;
    private readonly IServiceProvider _serviceProvider;
    private readonly IActionDescriptorCollectionProvider _collectionProvider;
    private readonly IActionInvokerFactory _invokerFactory;
    private readonly PageLoader _pageLoader;
    private readonly ILogger<HeatUpService> _logger;

    public HeatUpService(
        IServiceProvider serviceProvider,
        IActionDescriptorCollectionProvider collectionProvider,
        IActionInvokerFactory invokerFactory,
        PageLoader pageLoader,
        ILogger<HeatUpService> logger)
    {
        _jwtConfiguration = jwtConfiguration;
        _serviceProvider = serviceProvider;
        _collectionProvider = collectionProvider;
        _invokerFactory = invokerFactory;
        _pageLoader = pageLoader;
        _logger = logger;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var actions = _collectionProvider.ActionDescriptors;

        var periodicTimer = new PeriodicTimer(TimeSpan.FromMinutes(5));
        do
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var httpContext = new DefaultHttpContext
                {
                    RequestServices = scope.ServiceProvider
                };

                foreach (var descriptor in actions.Items)
                {
                    var route = new RouteData();
                    foreach (var pair in descriptor.RouteValues)
                        route.Values.Add(pair.Key, pair.Value);

                    var context = new ActionContext(httpContext, route, descriptor);
                    _invokerFactory.CreateInvoker(context);
                }
            }
            
            await periodicTimer.WaitForNextTickAsync(stoppingToken);
        } while (!stoppingToken.IsCancellationRequested);

    }
}
解决方案

1. 现有预热代码无效的原因

你当前的代码仅创建了ActionInvoker实例,但并未触发ASP.NET Core路由系统的核心逻辑——路由模板编译、路由树构建以及匹配逻辑的JIT编译,这些才是冷启动时路由匹配耗时的根源。

2. 针对性预热方案

方案一:模拟真实HTTP请求触发全流程

直接发送真实请求到服务端点,完整触发路由匹配、控制器激活等环节,是最有效的预热方式:

public class HeatUpService: BackgroundService
{
    private readonly HttpClient _httpClient;
    private readonly IActionDescriptorCollectionProvider _collectionProvider;
    private readonly ILogger<HeatUpService> _logger;

    public HeatUpService(HttpClient httpClient, 
                        IActionDescriptorCollectionProvider collectionProvider,
                        ILogger<HeatUpService> logger)
    {
        _httpClient = httpClient;
        _collectionProvider = collectionProvider;
        _logger = logger;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 根据路由规则生成所有端点URL(需结合实际路由模板调整)
        var endpoints = _collectionProvider.ActionDescriptors.Items
            .Select(d =>
            {
                var controller = d.RouteValues["controller"];
                var action = d.RouteValues["action"];
                return $"/{controller}/{action}";
            })
            .Distinct();

        var periodicTimer = new PeriodicTimer(TimeSpan.FromMinutes(5));
        do
        {
            foreach (var endpoint in endpoints)
            {
                try
                {
                    // 用HEAD请求减少服务端处理压力
                    await _httpClient.SendAsync(
                        new HttpRequestMessage(HttpMethod.Head, endpoint), 
                        stoppingToken
                    );
                    _logger.LogInformation("预热端点:{Endpoint}", endpoint);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "预热端点{Endpoint}失败", endpoint);
                }
            }
            
            await periodicTimer.WaitForNextTickAsync(stoppingToken);
        } while (!stoppingToken.IsCancellationRequested);
    }
}

注意事项:

  • 需为HttpClient配置服务自身的BaseAddress(如http://localhost:5000)
  • 若端点需认证,需在请求中添加对应Header(如JWT Token)
  • 优先使用HEAD请求,避免处理响应体

方案二:主动触发路由匹配逻辑的JIT编译

直接调用路由匹配相关方法,触发核心逻辑的编译:

public class HeatUpService: BackgroundService
{
    private readonly IRouteMatcher _routeMatcher;
    private readonly IEndpointRouteBuilder _endpointRouteBuilder;
    private readonly ILogger<HeatUpService> _logger;

    public HeatUpService(IRouteMatcher routeMatcher, 
                        IEndpointRouteBuilder endpointRouteBuilder,
                        ILogger<HeatUpService> logger)
    {
        _routeMatcher = routeMatcher;
        _endpointRouteBuilder = endpointRouteBuilder;
        _logger = logger;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var periodicTimer = new PeriodicTimer(TimeSpan.FromMinutes(5));
        do
        {
            var routeValues = new RouteValueDictionary();
            var httpContext = new DefaultHttpContext();
            
            // 遍历所有路由端点,触发匹配逻辑
            foreach (var endpoint in _endpointRouteBuilder.DataSources.SelectMany(s => s.Endpoints))
            {
                if (endpoint is RouteEndpoint routeEndpoint)
                {
                    try
                    {
                        await _routeMatcher.MatchAsync(httpContext, routeEndpoint.RoutePattern, routeValues);
                    }
                    catch
                    {
                        // 忽略匹配失败,仅为触发JIT编译
                    }
                }
            }
            
            _logger.LogInformation("路由系统预热完成");
            await periodicTimer.WaitForNextTickAsync(stoppingToken);
        } while (!stoppingToken.IsCancellationRequested);
    }
}

3. Kubernetes层面优化

除应用内预热,还可通过Kubernetes配置避免容器闲置:

  • 设置livenessProbe和readinessProbe,定期发送请求保持容器活跃
  • 调整HPA扩缩容阈值,避免Pod被缩容至0
  • 若场景适配,可改用Google Cloud Cloud Run,它提供更灵活的预热机制

内容的提问来源于stack exchange,提问作者szogun1987

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 12:08:08