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

高请求量下同步ActionFilter导致响应耗时过高?技术问询

问题分析与解决方案

你的同步ActionFilter确实可能是导致OnActionExecuted环节耗时过高的核心原因之一,同时日志操作本身的开销在高并发场景下会被进一步放大,具体原因和优化方案如下:

核心原因

  • 同步日志阻塞请求线程:如果LogDebug是同步写入(比如写入文件、数据库),每个请求都要等待日志操作完成才能结束,会持续占用请求线程。高并发下,大量线程被阻塞在日志写入环节,线程池资源快速耗尽,后续请求只能排队等待空闲线程,直接拉长了EndRequest的整体耗时。
  • 匿名对象序列化开销:{@info}语法会触发对象的完整序列化,尤其是context.Result可能包含大量业务数据,同步序列化过程会占用当前请求线程的CPU时间,单请求耗时增加,高并发下整体性能会急剧下降。
  • 同步Filter的执行特性:IActionFilter的同步方法运行在请求处理的主线程上,同步阻塞操作会直接延长请求的整体响应时间,且无法利用线程池的异步调度能力来优化资源利用。

优化方案

1. 改用异步ActionFilter

实现IAsyncActionFilter,将日志操作异步化,避免阻塞请求线程:

public class LoggerActionFilter : IAsyncActionFilter
{
    private readonly ILogger<LoggerActionFilter> _logger;

    public LoggerActionFilter(ILogger<LoggerActionFilter> logger)
    {
        _logger = logger;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var stopwatch = Stopwatch.StartNew();
        
        // 记录请求开始信息
        var request = context.HttpContext.Request;
        var startInfo = new
        {
            contentType = request.ContentType,
            url = request.Path.Value,
            method = request.Method,
            body = context.ActionDescriptor?.Parameters?.ToDictionary(x => x.Name, x => context.ActionArguments[x.Name])
        };
        _logger.LogDebug(LogEvents.StartRequest, "{@info}", startInfo);

        // 执行后续管道(包含业务Action)
        var executedContext = await next();

        // 记录请求结束信息
        stopwatch.Stop();
        var response = executedContext.HttpContext.Response;
        var endInfo = new
        {
            timeTaken = stopwatch.Elapsed.TotalMilliseconds,
            contentType = request.ContentType,
            status = response.StatusCode,
            url = request.Path.Value,
            // 仅记录Result类型,避免序列化大对象
            resultType = executedContext.Result?.GetType().Name
        };

        // 优先使用异步日志方法(需日志框架支持)
        if (_logger is IAsyncLogger asyncLogger)
        {
            await asyncLogger.LogDebugAsync(LogEvents.EndRequest, "{@info}", endInfo);
        }
        else
        {
            _logger.LogDebug(LogEvents.EndRequest, "{@info}", endInfo);
        }
    }
}

2. 配置异步日志写入

确保你的日志框架(如Serilog、NLog)使用异步目标,将日志操作转移到后台线程,完全避免阻塞请求线程:

  • Serilog:用WriteTo.Async包装输出目标
    Log.Logger = new LoggerConfiguration()
        .WriteTo.Async(a => a.File("logs/log-.txt", rollingInterval: RollingInterval.Day))
        .CreateLogger();
    
  • NLog:配置异步目标
    <targets>
      <target xsi:type="AsyncWrapper" name="asyncFile">
        <target xsi:type="File" name="file" fileName="logs/log-.txt" />
      </target>
    </targets>
    

3. 减少序列化开销

  • 避免序列化context.Result这类可能包含大量数据的对象,只记录必要的元数据(如Result类型、状态码)
  • 提前过滤请求参数中的敏感或冗余数据,减少序列化的数据量

4. 考虑用中间件替代ActionFilter

中间件的异步支持更原生,执行时机更灵活,适合高并发场景下的请求统计。示例:

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();
        var request = context.Request;

        // 记录请求开始
        var startInfo = new
        {
            contentType = request.ContentType,
            url = request.Path.Value,
            method = request.Method
        };
        _logger.LogDebug(LogEvents.StartRequest, "{@info}", startInfo);

        // 执行后续中间件
        await _next(context);

        // 记录请求结束
        stopwatch.Stop();
        var endInfo = new
        {
            timeTaken = stopwatch.Elapsed.TotalMilliseconds,
            status = context.Response.StatusCode,
            url = request.Path.Value
        };
        if (_logger is IAsyncLogger asyncLogger)
        {
            await asyncLogger.LogDebugAsync(LogEvents.EndRequest, "{@info}", endInfo);
        }
        else
        {
            _logger.LogDebug(LogEvents.EndRequest, "{@info}", endInfo);
        }
    }
}

额外注意点

  • 监控线程池状态:通过ASP.NET Core Metrics查看线程池活跃线程数、队列长度,确认是否存在线程耗尽的情况
  • 避免在Filter/Middleware中执行任何IO密集型或CPU密集型同步操作,所有耗时操作都应异步化

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 12:07:52