当前位置: 首页 > news >正文

应用服务(Web App)实战:用 .NET 代码把 Connection 耗尽与 SNAT 耗尽演练一次

概念还是抽象,所以我做了一个 .NET Demo,把问题拆成四个小实验:

  • 实验 1:Connection 耗尽 — 每次 new HttpClient()
  • 实验 2: Connection 优化 — IHttpClientFactory 复用
  • 实验 3: SNAT 耗尽 — 关闭连接池 + Connection: close
  • 实验 4: SNAT 优化 — 单例 HttpClient + MaxConnectionsPerServer ≤ 128

问题解答:

实验 1:让 App Service Instance 的出站连接快速耗尽

反例很简单:每个请求都new HttpClient(),而且不复用、不释放。

这样每个请求都会带来新的 handler 和连接池,短时间内大量并发时,worker 上的 TCP 连接资源会迅速堆积。

实验1的代码片段:

// BAD: new HttpClient 每次都创建,handler 与 socket 累积 app.MapGet("/api/demo/connection-bad", async ( int count, int concurrency, string? url) => { return await Runner.RunAsync(count, concurrency, async _ => { var client = new HttpClient(); // 每次新建 using var resp = await client.GetAsync(url); resp.EnsureSuccessStatusCode(); }); });

异常错误信息:
HttpRequestException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. (blog.mylubu.com:443) --> SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
实验结果截图:

实验 2:Connection 优化:用单例HttpClient/IHttpClientFactory复用

优化思路是:只保留少量长期存活的连接,让请求复用这些连接。

  • 复用HttpClient或使用IHttpClientFactory
  • PooledConnectionLifetime定期刷新连接,避免 DNS 漂移;
  • MaxConnectionsPerServer控制到同一目标的物理连接数。
实验2的代码片段:

// GOOD: 在 DI 中注册一次 builder.Services.AddHttpClient("pooled", c => c.Timeout = TimeSpan.FromSeconds(30)) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(2), // 解决 DNS 漂移 MaxConnectionsPerServer = 20, // 受限连接池 }); app.MapGet("/api/demo/connection-good", async ( int count, int concurrency, string? url, IHttpClientFactory factory) => { var client = factory.CreateClient("pooled"); // 从工厂复用 return await Runner.RunAsync(count, concurrency, async _ => { using var resp = await client.GetAsync(url); resp.EnsureSuccessStatusCode(); }); });

关键优化(vs 实验 1)
  • 不再 new HttpClient():用IHttpClientFactory.CreateClient("pooled")拿到共享实例。
  • 配置 PooledConnectionLifetime = 2min:定期回收连接,避免 DNS 漂移问题。
  • 配置 MaxConnectionsPerServer = 20(可在上方参数区动态调节):把单一目的端的并发物理连接控制在安全水位。
  • 结果:N 个 HTTP 请求 ↔ 至多 20 条物理 TCP 流,socket 不再泄漏。
实验结果截图:

实验 3:让 App Service Instance 的 SNAT Port 耗尽

Connection 优化解决的是 worker 本地资源,但 SNAT 是另一层限制。

只要每个 HTTP 请求都是一条新的 TCP 流,出站负载均衡器仍然要不断分配新的 SNAT 端口。

App Service 单实例通常按128 个 SNAT 端口估算,耗尽后新连接会卡住直到超时。

这个反例通过禁用连接池 +Connection: close,强制每个请求都新建 TCP 连接。

实验3的代码片段:

// BAD: 禁用连接池 + Connection: close => 每个请求都是一条全新 TCP 流 app.MapGet("/api/demo/snat-bad", async ( int count, int concurrency, string? url) => { return await Runner.RunAsync(count, concurrency, async _ => { using var handler = new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.Zero, // 禁用连接池 }; using var client = new HttpClient(handler); client.DefaultRequestHeaders.ConnectionClose = true; // 强制断开 using var resp = await client.GetAsync(url); resp.EnsureSuccessStatusCode(); }); });

异常错误信息:

HttpRequestException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. (blog.mylubu.com:443)
-->
SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.

实验结果截图:实验测试SNAT的端口占用数 > 128 个

实验 4:SNAT 优化:Keep-Alive 复用 +MaxConnectionsPerServer ≤ 128

优化方式也很直接:保留连接池,允许请求复用已有 TCP 连接。

  • 去掉Connection: close,保留 Keep-Alive;
  • 启用连接池,不再把PooledConnectionLifetime设为Zero
  • 控制MaxConnectionsPerServer,让同一目标的物理连接数低于 SNAT 安全水位。
实验4的代码片段:

// GOOD: 注册单例 HttpClient,所有请求共享一个连接池 builder.Services.AddSingleton<SharedHttpClient>(_ => { var handler = new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(2), // 启用连接池 PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30), // 空闲回收 MaxConnectionsPerServer = 20, // <= 128 }; return new SharedHttpClient(new HttpClient(handler)); }); app.MapGet("/api/demo/snat-good", async ( int count, int concurrency, string? url, SharedHttpClient shared) => { return await Runner.RunAsync(count, concurrency, async _ => { // 不设置 ConnectionClose => keep-alive 复用 using var resp = await shared.Client.GetAsync(url); resp.EnsureSuccessStatusCode(); }); });

关键优化(vs 实验 3)
  • 移除 Connection: close:保留 keep-alive,让服务端不会立刻关闭连接。
  • 启用连接池:PooledConnectionLifetime = 2min(而不是Zero)。
  • 添加 PooledConnectionIdleTimeout = 30s:空闲连接超时回收,但活跃连接长保留。
  • MaxConnectionsPerServer = 20(可在上方参数区动态调节):硬上限,远低于 128 SNAT 安全估算,确保不会撞墙。
  • HttpClient 注册为 Singleton:整个进程共享一个,所有请求复用同一连接池。
实验结果截图:

总结:

在以上实验中,观察App Service的Connects指标变动,当服用链接后,肉眼可见connections指标的快速下降。

http://www.jsqmd.com/news/1091705/

相关文章:

  • 基于Feign+Resilience4j的微服务熔断防雪崩优化方案
  • 英雄联盟Akari助手:从手忙脚乱到从容不迫的游戏效率革命
  • Tribler安全漏洞响应实战:从预警到部署的完整操作手册
  • 如何彻底修复Windows更新失败?Reset Windows Update Tool终极解决方案
  • 金库·封条·记分牌:SHE 安全硬件密钥防护体系深度解析
  • 完全免费的QModMaster:你的终极ModBus调试解决方案
  • 百度网盘秒传转存终极指南:3分钟掌握全平台快速分享技巧
  • 面试官坏笑:“你用 Claude Code 写代码,不怕它把项目搞炸?”,我:“怕,所以 CLAUDE.md、权限和验证,一个都不能少。”
  • 为什么你的电脑风扇总是“直升机模式“?这款开源智能散热管理工具让你彻底掌控温度与噪音
  • 深度学习十大归一化方法:两大阵营体系完整精讲
  • ChatGPT Go客户端安全加固手册:TLS双向认证、token轮换、审计日志全覆盖(附可审计代码模板)
  • Python+Pytest+Requests+Allure构建企业级接口自动化测试框架实战
  • billd-desk深度解析:如何构建跨平台WebRTC远程控制系统的技术架构
  • FDE课程标准:FDE+Code+skills
  • 力扣146LRU缓存
  • 自动点击器下载安装教程【超详细】安卓连点器保姆级图文教程(附安装包)
  • 我为什么研究RAGFlow:RuyiBookCourse遇到复杂文档解析后必须想清楚的事
  • 免费解锁WeMod专业版:Wand-Enhancer完全使用指南
  • 3min手搓一个帮助文档,很合理吧!
  • Simcenter STAR-CCM+安装步骤(附安装包)STAR-CCM+ 超详细下载安装教程
  • libuvc实战:跨平台USB摄像头控制与多设备区分
  • 如何深度掌控AMD Ryzen处理器:SMU Debug Tool完整指南
  • 人工智能大模型兵棋推演系统软件平台:有哪些优点和缺点
  • 先说个常见的情况
  • BurpSuite 2023+ 上游代理配置实战:告别UserOptions,拥抱Settings新路径
  • NFS服务安全加固:从CVE-1999-0554漏洞看showmount信息泄露的深度防御
  • 射频工程师实战指南:S参数、OP1dB、IMD与NF的测量要点与校准技巧
  • 如何在 Python 项目中避免循环引用
  • 关于防范利用非主流二级域名进行钓鱼攻击的风险提示
  • SetDPI深度解析:Windows DPI缩放管理的命令行艺术