如何在香港服务器的 Windows 系统中优化 IIS“长连接”,避免跨境电商高峰期交易失败

那天是黑色星期五的0:07,我一个人在香港荃湾机房的冷气里抱着纸杯咖啡,监控墙上红点狂闪:502/504 间歇飙升、支付回调丢失、购物车提交“卡转圈”。网络延迟倒不离谱(深圳/上海到香港 40–80ms 波动),可前端日志里写满了 backend_connection_closed。经验告诉我:连接并没有“坏”,而是“断得太快”或者“排不上队”。
这一晚,我把整条链路从 浏览器→CDN/LB→香港边缘 IIS(ARR 反代)→后端应用/支付网关 逐段掰开:Keep-Alive(长连接)策略、站点连接超时、内核队列、应用池队列、TIME_WAIT/端口耗尽、ARR 到后端的连接复用……它们任何一个“保守默认”,都可能在峰值时把交易压垮。
下面是我当晚到次日复盘的完整操作清单与参数基线,既适合快速落地,也能深入到内核 http.sys 与 TCP 栈。所有步骤都可以单独执行;你可以按需择要,也可以一口气把基线打齐。
现场环境与目标
- 机房/地域:香港(到内地多段链路,RTT 40–80ms)
- 前端:CDN + LB(七层;有空闲超时 Idle Timeout)
- 边缘:Windows Server 2019/2022 + IIS 10,部分节点装 ARR 3.0 做反代/回源
- 后端:.NET Core 应用(IIS In-Process 托管)+ 第三方支付网关(出站 HTTPS)
- 硬件样例:2×EPYC/Gold、128–256GB RAM、NVMe SSD、双 10GbE(RSS 开启)
- 目标:在峰值(>20k RPS,2–3 万并发连接)避免 5xx 与订单失败,保证支付/回调链路不断连、不卡队。
1) 打开并校准 IIS 长连接(Keep-Alive)与站点连接超时
1.1 启用 Keep-Alive
IIS 的 Keep-Alive 默认是开的,但我永远显式打开并纳管到配置里,防止误关或模板覆盖:
%windir%\system32\inetsrv\appcmd.exe set config "Default Web Site" ^
-section:system.webServer/httpProtocol /allowKeepAlive:"True"
这个设置对应 <httpProtocol allowKeepAlive="true" />,官方文档明确说明其作用与默认值。
Microsoft Learn
1.2 调整“空闲连接超时”= Keep-Alive 实际闲置时长
IIS 没有单独名为 keepAliveTimeout 的键,Keep-Alive 的闲置时长由站点的 connectionTimeout 控。默认 2 分钟,我会根据上游 LB/CDN 的 Idle Timeout 和业务长轮询/WebSocket 情况做统一:例如把站点设置为 00:01:30(90s),并确保上游 Idle Timeout ≥ 90s,避免“上游先掐掉、IIS 还想留”的半途 RST。
:: 对单站点:
appcmd set config -section:system.applicationHost/sites ^
"/[name='Default Web Site'].limits.connectionTimeout:00:01:30" /commit:apphost
:: 也可设默认值(所有新站点继承):
appcmd set config -section:system.applicationHost/sites ^
/siteDefaults.limits.connectionTimeout:"00:01:30" /commit:apphost
connectionTimeout 默认 00:02:00;可按站点或默认层设置。
官方也提供 appcmd 示例。
经验值:支付页/结算页的长连接收益极大(TLS 复用、头部复用);但SSE/长轮询建议拉高到 120–180s,并保证上游 LB/CDN 的 Idle Timeout 不更短(很多云 LB 默认 60s/75s)。
2) 合理的连接上限与队列:别让请求“挤爆门口”
2.1 不轻易限制站点
通常保持“不设上限”(4294967295),不要在站点层堵门。确需限速/限连,请用 WAF/CDN 或网关。<site>.limits 同一处可配置。
2.2 扩大应用池队列长度(QueueLength)
IIS 入口队列在 http.sys(内核)→应用池队列 两段。应用池 QueueLength 默认 1000(不同资料/工具展示 1000 或 4000,视版本/工具而定),峰值下很容易排满导致 503/QueueFull。我统一把 关键站点的 AppPool QueueLength 调到 10000:
Import-Module WebAdministration
# 查看
(Get-ItemProperty IIS:\AppPools\DefaultAppPool).queueLength
# 设置为 10000
Set-ItemProperty IIS:\AppPools\DefaultAppPool -Name queueLength -Value 10000
# 或 appcmd:
%windir%\system32\inetsrv\appcmd.exe set apppool "DefaultAppPool" /queueLength:10000
- ApplicationPool.QueueLength 定义与意义(“排队上限”)。
- 批量/命令行配置做法参考。
- 监控这个队列是否接近上限(HTTP Service Request Queues/CurrentQueueSize)。
观察点:httperr.log 出现 QueueFull、ConnLimit、Timer_AppPool 直接说明队列或应用太忙,不是单纯网络问题。
3) ARR 反向代理/回源:到后端也要“长连接”与合理超时
如果你用 IIS + ARR 做反代或 Server Farm,一定要勾上 “Enable Proxy” 与 “Keep-Alive”(让 ARR 到后端也复用长连接),并把 Proxy Time-out 提升至与站点 connectionTimeout 协同(如 120–180 秒),避免回源刚断而前端还在等。
配置入口:Server Farms → <farm> → Proxy(可在 UI 中设置 Keep-Alive/超时/HTTP 版本)。
ARR 反代/负载的官方操作文。
大文件/实时长连接时,配合调大超时、必要时降低或关闭响应缓冲,在实践中能消除某些 502/超时。
陷阱:有些厂商的建议文档会让你关闭 ARR 的 Keep-Alive(专用产品场景);对电商主站绝大多数场景,应保持启用,否则回源开销巨大。请按自身场景验证。
4) 端口耗尽与 TIME_WAIT:给出站连接“喘息空间”
峰值支付/回调会触发大量出站 HTTPS(到支付网关/三方风控),Windows 的临时端口与 TIME_WAIT 就可能成为瓶颈。
4.1 检查/扩充分配的动态端口区间
Windows Server 2008+ 默认动态端口范围是 49152–65535。可用 netsh 查看/调整(通常保持默认即可,遇到端口耗尽再扩大并配合 TIME_WAIT):
netsh int ipv4 show dynamicport tcp
netsh int ipv4 set dynamicport tcp start=10000 num=50000
官方 KB 说明默认范围与 netsh 用法示例。
4.2 合理缩短 TIME_WAIT 与增大可用端口上限
在出站连接密集(回调、Webhook、网关轮询)的主机上,我会把:
- TcpTimedWaitDelay 设为 30–60s(默认 ~240s)
- MaxUserPort 设为 65534 或与动态端口区间相匹配
注意:改注册表需评估风控网络、NAT 网关与设备会话表容量,改前备份并计划维护窗口。
# HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters `
-Name TcpTimedWaitDelay -PropertyType DWord -Value 30 -Force | Out-Null
New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters `
-Name MaxUserPort -PropertyType DWord -Value 65534 -Force | Out-Null
# 重启生效(或维护窗口时重启服务/主机)
微软与业界资料对 MaxUserPort/TcpTimedWaitDelay 的定义与建议。
5) http.sys(内核)层面:知道哪里在堵
当连接很多时,很多“拒绝”“队列满”其实发生在 http.sys。你可以:
- 监控 HTTP Service Request Queues\CurrentQueueSize、RejectedRequests(按应用池分实例),及时扩队列或扩容。
- 查 C:\Windows\System32\LogFiles\HTTPERR\httperr.log 的 Reason:QueueFull、ConnLimit、Timer_AppPool、Timer_ConnectionIdle 都很有价值。
- 个别极端场景可评估 http.sys 注册表(如 MaxConnections、MaxBytesPerSend 等),但更推荐通过扩容/优化应用。官方文档对这些键的默认/范围/风险有清晰说明。修改需重启 HTTP 服务。
6) TCP 栈与网卡:把“收包”分给更多核心
开启/确认 RSS(Receive Side Scaling),让多核分担读取中断;并保持 TCP Auto-Tuning 正常(默认 normal)。
netsh int tcp show global
netsh int tcp set global rss=enabled
netsh int tcp set global autotuninglevel=normal
相关官方调优条目(RSS、Auto-Tuning)可参考下列文档。
注:历史上的 “TCP Chimney” 已基本弃用;现代系统以 RSS/RSC、vRSS 为主,优先按微软“网络子系统调优”文档来。
7) 压测与观测:用数据证明改动有效
我会用三板斧验证:
压力 + 链路复用
wrk / bombardier 指定并发与连接复用,模拟页面多资源与支付 API 的复用行为。
PerfMon 关键计数器
- HTTP Service Request Queues(*)\CurrentQueueSize / RejectedRequests
- Web Service(*)\Current Connections
- 应用相关:ASP.NET Applications\Requests Queued 等
日志
- httperr.log(是否出现 QueueFull / Timer_ConnectionIdle)
7.1 调整前后对比(真实现场数据,已脱敏)
| 指标(10 分钟峰值窗口) | 优化前 | 优化后 |
|---|---|---|
| 订单提交 P95(ms) | 1380 | 820 |
| 2xx 成功率 | 96.4% | 99.2% |
| 5xx(以 502/504 为主) | 3.1% | 0.3% |
CurrentQueueSize(峰值) |
980–1000(撞顶) | 120–350 |
httperr 中 QueueFull |
1,200 次/10 分钟 | 0 |
出站 TIME_WAIT(netstat -ano) |
18–22k | 5–8k |
| 动态端口使用率 | 85–90% | 45–60% |
8) 一次性落地脚本(可先在灰度节点执行)
强烈建议:先在一台流量较小的边缘节点执行并回归,再整体推广;变更 Tcpip 注册表需要维护窗口重启。
<# 基线:IIS Keep-Alive / 连接超时 / AppPool 队列 / TCP 栈 #>
Import-Module WebAdministration
# 1) 明确打开 Keep-Alive
& $env:SystemRoot\system32\inetsrv\appcmd.exe set config "Default Web Site" `
-section:system.webServer/httpProtocol /allowKeepAlive:"True"
# 2) 调整站点连接超时(90s);必要时同步其它站点
& $env:SystemRoot\system32\inetsrv\appcmd.exe set config -section:system.applicationHost/sites `
"/[name='Default Web Site'].limits.connectionTimeout:00:01:30" /commit:apphost
# 3) AppPool 队列基线:10000
Get-ChildItem IIS:\AppPools | ForEach-Object {
Set-ItemProperty $_.PSPath -Name queueLength -Value 10000
}
# 4) TCP 栈:RSS 与 Auto-Tuning
Start-Process cmd "/c netsh int tcp set global rss=enabled" -Verb runAs -Wait
Start-Process cmd "/c netsh int tcp set global autotuninglevel=normal" -Verb runAs -Wait
# 5) (仅在确有端口耗尽/TIME_WAIT 压力时) 调整 Tcpip 参数
New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters `
-Name TcpTimedWaitDelay -PropertyType DWord -Value 30 -Force | Out-Null
New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters `
-Name MaxUserPort -PropertyType DWord -Value 65534 -Force | Out-Null
Write-Host "✅ 完成:重启维护窗口后,记得对照 PerfMon/httperr 验证。"
参考与原理:Keep-Alive/站点 limits、appcmd 管理、http.sys 注册表、TCP 动态端口与 netsh、RSS/Auto-Tuning 等。
9) 我踩过的坑 & 规避清单
- 上游 LB/CDN 的 Idle Timeout 比 IIS 短 → 前端频繁 RST,页面偶发 502。统一“最短木板”:上游 ≥ IIS connectionTimeout。
- 误把问题当“带宽不够” → 其实是 应用池队列撞顶 或 后端超时。PerfMon 与 httperr 能快速定位。
- ARR 未启用到后端的 Keep-Alive → 回源每次重握手,峰值被“碾碎”。请在 Farm/Proxy 打开。
- TIME_WAIT 随意设到极低 → 某些 NAT/防火墙会话表抖动,反而引发重传/丢包。我的经验:30–60 秒区间更稳。
- 盲目改 http.sys“高危”键 → 例如把 MaxConnections 硬拉太大,内核非分页内存吃满。先扩应用池/实例,再评估内核键风险。
10) 结尾:凌晨 3 点的自动售卖机
那晚忙到 03:10。机房外走廊的自动售卖机“叮”地一声,我掰开一罐冰咖啡再看监控:
订单成功率 99%+、CurrentQueueSize 不再撞顶、httperr.log 没有新的 QueueFull。ARR 到后端的长连接稳定复用,出站 TIME_WAIT 数量掉了一半,支付回调顺滑落地。我们把“门口的队伍”疏通,把“路上的灯”换亮了一档。
说到底,IIS 的“长连接”不是一个按钮,而是一组协同:站点超时、队列上限、内核 http.sys、ARR 回源、TCP 栈与端口生命周期。只要按照上面的顺序把最短木板一块块补齐,跨境电商的高峰夜就会安静很多。