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

ASP.NET Core 2.2 API请求体读取超时问题排查求助

ASP.NET Core 2.2 REST API生产环境偶发请求超时问题排查与解决建议

问题背景

生产环境偶发错误:

Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate

生产环境同时存在快速与慢速移动网络请求,测试环境通过Jmeter将请求限速至2048字节/秒(该速率本不应触发Kestrel默认240B/s的MinRequestBodyDataRate限制),以最多6个并发请求的中等负载复现了该错误。

异常抛出在自定义TextInputFormatter类的JArray.Load调用处,代码如下:

public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
        var stream = context.HttpContext.Request.Body;

        using (var reader = context.ReaderFactory(stream, encoding))
        using (var jsonReader = new JsonTextReader(reader))
        {
            // Don't close the request stream because you many need it in other middleware or controllers.
            jsonReader.CloseInput = false;

            bool isSuccessful = true;
            List<BaseCommand> model = null;

            try
            {
                Assembly assembly = typeof(BaseCommand).Assembly;

                // here we're reading and parsing the json array directly from the stream
                var objCollection = JArray.Load(jsonReader);
                Log.Information("Json message:{0}", JsonConvert.SerializeObject(objCollection));

                model = objCollection
                    .Select((obj) =>
                    {
                        // here some code mapping the incoming data to the internal type BaseCommand
                        // ...

                        return mappedCmd;
                    }).ToList();
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error parsing BaseCommand");
                isSuccessful = false;
            }

            if (isSuccessful)
            {
                if (model == null && !context.TreatEmptyInputAsDefaultValue)
                    return InputFormatterResult.NoValueAsync();
                else
                    return InputFormatterResult.SuccessAsync(model);
            }

            return InputFormatterResult.FailureAsync();
        }
}

并发表现

当服务器存在5-6个活跃请求时,错误率约为12%;典型JSON payload大小在1kB至20kB之间。取消Jmeter限速后,服务器活跃请求数维持在1左右,无错误产生。

已尝试的无效方案:

  • 将JArray.Load()替换为异步的await JArray.LoadAsync()
  • 将线程池最小线程数从1调整为10

服务器资源情况:CPU从未超过15%,内存从未超过10%,API托管在基于Alpine Linux镜像的Docker容器中。

问题疑问

请问是否我的代码存在性能问题,还是应限制入站请求数来避免该错误?


分析与解决建议

核心原因分析

  1. Kestrel速率限制的计算逻辑:MinRequestBodyDataRate是基于整个请求体读取周期的平均速率,而非瞬时速率。如果服务器线程被占用导致请求体读取被阻塞,即使客户端发送速率达标,平均速率仍可能低于阈值触发超时。
  2. 自定义Formatter的同步操作阻塞IO线程:即使将JArray.Load改为异步,后续的Select.ToList()是同步CPU绑定操作,会占用IO线程,导致Kestrel无法及时处理请求体读取,拖慢整体速率。

具体解决步骤

  1. 优化自定义InputFormatter的异步处理
    把映射逻辑改为异步,避免在IO线程上执行同步CPU操作,确保IO线程尽快释放回线程池:

    // 替换原有的Select.ToList()逻辑
    var tasks = objCollection.Select(async obj => {
        // 这里改为异步的映射逻辑
        // ...
        return mappedCmd;
    });
    model = await Task.WhenAll(tasks);
    
  2. 调整Kestrel请求体速率限制参数
    针对慢速网络场景,适当放宽速率限制或延长宽限期,给慢速连接更多缓冲时间:

    webBuilder.ConfigureKestrel(options =>
    {
        // 降低最低速率要求,延长宽限期
        options.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    });
    
  3. 检查Docker容器资源限制
    即使主机资源充足,Docker容器可能被限制了CPU核心数或线程数,导致线程池无法扩容处理并发。检查容器的CPU配额设置,适当增加可用核心数。

  4. 移除日志中的冗余序列化操作
    代码中JsonConvert.SerializeObject(objCollection)会对整个JArray重新序列化,带来额外CPU开销,并发时加剧线程占用。建议改为记录请求长度、元素个数等特征信息,而非完整序列化内容。

  5. 临时缓解:限制入站请求数
    如果以上优化无法立即生效,可通过HAProxy或Kestrel的并发限制功能,将活跃请求数控制在4以内,降低并发压力。但这只是临时方案,核心仍需优化代码与配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 20:32:39