未预热时ASP.NET 6并发Web请求触发异常的排查求助
ASP.NET 6 Linux部署下并发WebRequest偶发超时问题排查与解决思路
问题背景
在Linux系统部署的ASP.NET 6服务器中,为实现并发WebRequest自定义了BarrierTask类处理并行逻辑,但出现偶发请求超时失败:首次发起大并发请求时多数抛出System.IO.IOException: The response ended prematurely.异常,预热请求后恢复正常,服务器闲置数分钟后问题复现。
关键代码片段
启动阶段ThreadPool配置
System.Net.ServicePointManager.DefaultConnectionLimit = 500; System.Threading.ThreadPool.SetMinThreads(1000, 500);
并行WebRequests执行代码
BarrierTask<int, TestDto> barrierTask = new BarrierTask<int, TestDto>(); int[] arr = new int[count]; for (int i = 0; i < arr.Length; i++) { arr[i] = i; } TestDto[] results = barrierTask.Run((a) => { var id = new TestDto() { TimestampStart = DateTime.UtcNow, TimestampEnd = new DateTime(), Delay = delay, Name = $"Test request {a}", Error = "OK" }; string str = Newtonsoft.Json.JsonConvert.SerializeObject(id); try { string json = DoRequest("https://.../apidebug/delay", str); var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<TestDto>(json); return ret; } catch (Exception ex) { id.Error = ex.ToString().Replace("\r","\\r").Replace("\n","\\n"); id.TimestampEnd = DateTime.UtcNow; return id; } } , arr); string DoRequest(string uri, string body) { using var client = new HttpClient(); using (var webRequest = new HttpRequestMessage(HttpMethod.Post, uri) { Content = new StringContent(body, Encoding.UTF8, "application/json"), }) { using (var response = client.Send(webRequest)) { using (var reader = new StreamReader(response.Content.ReadAsStream())) { return reader.ReadToEnd(); } } } }
BarrierTask实现代码
public class BarrierTask<TArgs,TResult> { Barrier barrier; public TResult[] Run(Func<TArgs, TResult> action, TArgs[] args) { List<TResult> ret = new List<TResult>(); barrier = new Barrier(args.Length + 1); foreach (TArgs arg in args) { Task.Run(() => { try { TResult result = action(arg); lock (this) { ret.Add(result); } } catch (Exception ex) { AppLog.Log("BarrierTask.Run: Input: " + arg.ToString() + " Ex: " + ex.Message, AppLog.MessageType.Error); } finally { barrier.SignalAndWait(); } }); } barrier.SignalAndWait(); return ret.ToArray(); } }
目标端点代码
[Route("api/delay")] [ApiController] public class DelayController : ControllerBase { [HttpPost] public TestDto Post(TestDto testDto) { System.Threading.Thread.Sleep(testDto.Delay); testDto.TimestampEnd = DateTime.UtcNow; return testDto; } }
测试场景
- 服务器启动后直接发起100个延迟4000ms的并发请求:部分成功,多数抛出
System.IO.IOException: The response ended prematurely.异常,推测为Apache超时终止连接;二次请求正常。 - 先发起预热请求再执行100个并发请求:全部正常。
- 闲置数分钟后:需再次预热,否则部分请求仍异常。
解决思路
复用HttpClient实例:当前DoRequest方法每次创建新HttpClient,会导致频繁创建/释放TCP连接,Linux下TIME_WAIT状态的连接会占用资源,首次大并发时连接池未建立易触发超时。改用单例HttpClient或依赖注入IHttpClientFactory管理实例,复用HTTP连接池。
调整Apache超时配置:若确认是Apache侧主动断开连接,检查并调整
Timeout、ProxyTimeout参数,延长至覆盖请求延迟+网络耗时(比如设置为10s以上);同时优化KeepAliveTimeout和MaxKeepAliveRequests参数,提升长连接复用效率。优化并行逻辑:
- 替换
Task.Run无限制创建任务的方式,改用Parallel.ForEach并指定最大并行度(如new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 }),避免线程池过载。 - 用
ConcurrentBag<TResult>替代List<TResult>加锁添加元素,减少锁竞争开销。 - 确保Barrier的Signal逻辑在所有分支都能执行,避免主线程无限等待。
- 替换
优化TCP与HTTP参数:
- 设置
ServicePointManager.Expect100Continue = false,减少HTTP握手阶段的开销。 - 调整Linux系统TCP参数:开启
net.ipv4.tcp_tw_reuse = 1允许复用TIME_WAIT状态连接,缩短net.ipv4.tcp_fin_timeout时间,缓解连接资源不足问题。
- 设置
添加自动预热逻辑:在服务器启动后自动发起少量预热请求,提前初始化线程池并建立HTTP连接池,避免首次大并发请求时的资源初始化延迟。可在Program.cs中添加启动后执行的异步预热任务。
内容的提问来源于stack exchange,提问作者Jaromír Konečný
相关产品推荐
相关产品推荐

