如何在香港服务器使用 Windows Server 2012 配置 IIS 请求队列与连接超时,避免高并发下服务不可用

凌晨 2:17,我被短信网关的连续告警震醒——“5xx 比例异常”,“API P95 超 8s”,“队列溢出”。我把电脑合上,用惯性摸到机柜钥匙。香港葵涌 IDC 的空调像直升机螺旋桨一样轰鸣,监控屏里大陆多个节点对我们香港业务域名的 RTT 在 30\~60ms 左右,带宽曲线像心电图一样上蹿下跳。NOC 的同事说:“峰值到来了,支付回调在轰炸你们的 API。”
我第一眼就怀疑是HTTP.sys / IIS 请求队列顶不住了,再叠加连接超时配置偏保守,导致高并发下一部分请求还没来得及被工作进程取走就被 503 淘汰。我做过太多次这种夜战,今天把这套在 Windows Server 2012(IIS 8.0)上调优请求队列与连接超时的完整打法,原汁原味写下来。
环境与参数(真实现场)
| 项 | 说明 |
|---|---|
| 机房 | 香港葵涌,双线 BGP,99.99% 供电 SLA |
| 网络 | 上联 2×10Gb,公网划分 /27,业务站点走 Anycast + 四层转发(备用:Cloudflare 橙色云关闭) |
| 服务器 | 1U 独立服务器 × 4(本节以主节点 A 叙述) |
| 主机型号 | Dell R230(单路) |
| CPU | Intel® Xeon® E3-1270 v6,4C/8T |
| 内存 | 32 GB DDR4 |
| 磁盘 | 2 × 1.92TB SATA SSD(RAID1),系统与日志分卷 |
| 系统 | Windows Server 2012 Datacenter(IIS 8.0) |
| 角色 | IIS(应用程序池:Integrated 模式,.NET 4.6.2),Application Initialization 已安装 |
| 业务 | API(JSON),峰值 4–6k RPS,长尾偶有慢客户端(移动网络) |
> 注:如果你是 2012 R2(IIS 8.5),设置项名称相同或高度一致,下文同用。
现象与基本判断
- 访问日志可见间歇性 503(Service Unavailable),平均响应时间上升。
- 性能计数器 `HTTP Service Request Queues/CurrentQueueSize` 瞬时飙升。
- `Requests Rejected` 增加,Event Viewer 偶见 WAS 警告(池响应变慢)。
- 业务特征:突发并发 + 少量慢连接(上传、弱网)。
推断:
- 应用程序池的队列长度(queueLength)偏小,突发时溢出。
- 连接超时(connectionTimeout)、最小传输速率(minBytesPerSecond)等参数未针对弱网与慢请求做平衡,导致资源被“慢连接”占用过久。
背景图谱:一次请求在 IIS 内部怎么走?
TCP 连接 → HTTP.sys(内核态队列)→ 应用程序池队列(按池隔离) → w3wp 工作进程(用户态)→ ASP.NET 管道/业务代码 → 响应
- 队列溢出会直接 503;
- 连接超时既包括客户端空闲导致的断开,也包括服务器为了避免“慢速攻击/慢客户端”而设置的最小字节速率策略;
- 我们要做的是:让突发时队列能“兜住”请求,同时用合理的连接超时策略清走占坑不干活的连接。
调优目标
- 队列不丢:合理放大 `queueLength`,用“瓦桶”接住突发洪峰;
- 慢即断:对慢速/空闲连接设置“人性化但不纵容”的超时;
- 业务优先:把 CPU 用在有产出的请求上,避免被慢连和大文件长时间占满;
- 可观测:上线前后可量化验证,有回滚手段。
操作前的“三件套”
:: 1) 备份 IIS 总配置
%windir%\system32\inetsrv\appcmd add backup "pre-queue-tuning"
:: 2) 导出站点与池清单
%windir%\system32\inetsrv\appcmd list apppool /config > C:\ops\apppools.before.xml
%windir%\system32\inetsrv\appcmd list site /config > C:\ops\sites.before.xml
:: 3) 打点监控(性能计数器组建议)
:: HTTP Service Request Queues\CurrentQueueSize
:: HTTP Service Request Queues\Requests Rejected
:: Web Service(_Total)\Current Connections
:: ASP.NET Applications(__Total__)\Requests Queued
:: Process(w3wp)\% Processor Time
步骤一:计算与设置应用程序池队列长度(queueLength)
思路:按业务峰值速率和可接受排队时延来估算队列容量。
> 近似:`队列容量 ≈ 峰值 RPS × 可接受的排队秒数`
- 对 API 类业务,我一般取排队秒数 3–5s;
- 对页面/下载业务可酌情更大,但不建议无脑拉满。
示例估算:
峰值 5k RPS,允许 4s 排队 → 建议队列 ≈ 20,000。
落地命令:
:: 查看现有值(默认通常是 1000)
%windir%\system32\inetsrv\appcmd list apppool "ApiPool" /text:queueLength
:: 设置为 20000(范围通常 1~65535),并重启池生效
%windir%\system32\inetsrv\appcmd set apppool "ApiPool" /queueLength:20000
%windir%\system32\inetsrv\appcmd recycle apppool /apppool.name:"ApiPool"
> 经验:把所有对外 API 的池都设到同档位,后台任务池可略小;调大后务必盯延迟,队列不是保险箱,过大只会把延迟堆高。
步骤二:配置站点连接超时与最小传输速率
IIS 的站点 Limits 里有两把“刀”:
- `connectionTimeout`:客户端在无数据传输时允许保持的最长时间(通常默认约 120s)。
- `minBytesPerSecond`:最小传输速率门槛,低于门槛会被断开(防慢速攻击/弱网长期占坑)。
建议策略(API 业务):
- `connectionTimeout`:30–45s;
- `minBytesPerSecond`:240–480(字节/秒);
- 对大文件上传/下载的专用站点/路径单独放宽。
命令行配置:
:: 查看站点当前 limits
%windir%\system32\inetsrv\appcmd list site "ApiSite" /config
:: 设置连接超时 35s、最小传输速率 360 B/s
%windir%\system32\inetsrv\appcmd set site "ApiSite" /limits.connectionTimeout:00:00:35 /limits.minBytesPerSecond:360
:: 如需全局默认(影响新建站点)
%windir%\system32\inetsrv\appcmd set config -section:system.applicationHost/sites /siteDefaults.limits.connectionTimeout:"00:00:35" /siteDefaults.limits.minBytesPerSecond:360 /commit:apphost
> 注意:移动网络弱、海外弱网用户比例高时,`minBytesPerSecond` 不要太激进。对上传接口(如 OSS 直传回调、文件上报)单独建站点或路由,给它更大的超时窗口,别拖累核心 API。
步骤三:ASP.NET 相关超时与线程占用
`executionTimeout`(web.config):单个请求在 ASP.NET 管道里的最长执行时间(默认 \~110s)。API 通常60–90s 上限已经足够;更长的任务用后台作业/消息队列。
<!-- Web.config 根或对应应用级别配置 -->
<configuration>
<system.web>
<httpRuntime executionTimeout="90" maxRequestLength="51200" />
</system.web>
</configuration>
异步化 I/O:外呼下游(DB/HTTP)务必用 async/await,减少同步阻塞导致的队列堆积。
步骤四:预热与冷启动治理
高并发场景里,冷启动常被误判为“程序变慢”。
1. 安装并启用Application Initialization(2012 可用):
dism /online /enable-feature /featurename:IIS-ApplicationInit
2. 应用池与站点设置:
:: 池常驻(避免空闲被回收)
%windir%\system32\inetsrv\appcmd set apppool "ApiPool" /startMode:AlwaysRunning /processModel.idleTimeout:00:20:00
:: 站点预加载
%windir%\system32\inetsrv\appcmd set app "ApiSite/" /preloadEnabled:true
3. 可选:加一个暖机 URL(会话无副作用),配合计划任务在重启后打一次。
步骤五:建立观测与阈值告警(PowerShell)
以下脚本按分钟采样队列与拒绝数,超过阈值写事件日志(供 SIEM/监控抓取):
$instance = "apppool_ApiPool" # HTTP Service Request Queues 的实例名
$queueWarn = 2000 # 告警阈值(按业务调)
while ($true) {
$q = (Get-Counter "\HTTP Service Request Queues($instance)\CurrentQueueSize").CounterSamples.CookedValue
$r = (Get-Counter "\HTTP Service Request Queues($instance)\Requests Rejected").CounterSamples.CookedValue
if ($q -gt $queueWarn) {
Write-EventLog -LogName Application -Source "IISOps" -EventId 52001 -EntryType Warning -Message "Queue=$q, Rejected=$r"
}
Start-Sleep -Seconds 60
}
> 性能计数器另荐:`Web Service(*)/Current Connections`、`ASP.NET Applications(*)/Requests Queued`、`Process(w3wp)/*`。
步骤六:压测与回归
外部压测:在深圳/广州节点用 `wrk` / JMeter 打到香港,模拟真实链路 RTT 与抖动;
观察:
- 队列峰值是否下降且无溢出;
- P95 是否可控;
- 503 是否消失;
- 弱网下请求是否被合理淘汰(日志中的 400/ connections dropped)。
参数建议表(可直接抄作业)
> 仅供起步参考,最终以业务压测为准。
| 场景 | 建议 queueLength |
connectionTimeout |
minBytesPerSecond |
ASP.NET executionTimeout |
| 纯 API(JSON,小报文) | 15000–30000 | 30–45s | 240–480 B/s | 60–90s |
| 页面渲染(含静态资源) | 8000–15000 | 45–60s | 120–360 B/s | 90–120s |
| 大文件上传/下载 | 4000–8000(专站) | 120–600s | 60–120 B/s | 300–600s |
常见坑与我的填坑笔记
1.以为调大队列就万事大吉:队列会“隐藏”过载,延迟曲线先温和后断崖。要配合限流/降级与赎回机制(快速失败)。
2.忘了重启应用程序池:`queueLength` 调整后不重启,行为不一定即时一致。稳妥起见改完就 recycle。
3.`minBytesPerSecond` 太激进:导致移动端弱网频繁断开,客服投诉。针对上传下载单独站点放宽。
4.混用长短连接:长轮询/上传与核心 API 混在同一站点,连接超时不得不妥协。拆站点是最优解。
5.反代/边缘超时不匹配:CDN/四层或七层代理超时比源站短,外层先断开,用户仍是 5xx。统一各层超时策略。
6.冷启动误伤:空闲回收+无预热,突发时第一波用户当“替罪羊”。启用 AlwaysRunning + preload。
7.事件日志噪音:WAS 和 w3wp 的 Warning 太多时很难追溯。加自定义事件写入关键指标,回放时一眼找到峰值点。
进阶:按路径/站点做差异化超时
对 `/upload/*` 路径:web.config 层设置更长 `executionTimeout`,并在站点层放宽 `connectionTimeout`;
对 `/api/*`:保持较低 `connectionTimeout` 与较高 `minBytesPerSecond`,保护核心接口;
对 `/healthz`:短超时+快速返回,供负载均衡健康检查。
示例(仅对某应用生效):
<configuration>
<system.web>
<httpRuntime executionTimeout="300" />
</system.web>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="2147483648" />
</requestFiltering>
</security>
</system.webServer>
</configuration>
验收标准(我用这三条当“绿灯”)
1. 峰值 10 分钟内,`Requests Rejected` 始终为 0;
2. `CurrentQueueSize` 在可接受区间内波动(无长时间平台期);
3. 503 基本消失,P95/P99 达标,客户投诉量明显下降。
那天我把 `queueLength` 拉到 20000,核心 API 站点的 `connectionTimeout` 降到 35 秒,`minBytesPerSecond` 设 360。回收池、预热、跑一轮外部压测后,监控从红变绿。NOC 的小伙给我递了一杯 Vitasoy 咖啡牛奶,说:“哥,这回稳了。”
黎明前的香港港湾还在起雾,机房外的电梯叮地响了一下。我记下这套参数,留给后来者:拥抱队列,但不溺爱它;善待慢连接,但别被它拖垮。
如果你也在香港机房里为延迟和 503 奋战,希望这篇手记能帮你在关键的一夜,少走几个弯路。
附录:一次性批量脚本
批量设置队列与站点限额(PowerShell):
Import-Module WebAdministration
$poolNames = @('ApiPool','WebPool')
foreach ($p in $poolNames) {
Set-ItemProperty IIS:\AppPools\$p -Name queueLength -Value 20000
Restart-WebAppPool $p
}
Set-ItemProperty IIS:\Sites\ApiSite -Name limits.connectionTimeout -Value ([TimeSpan]::FromSeconds(35))
Set-ItemProperty IIS:\Sites\ApiSite -Name limits.minBytesPerSecond -Value 360
Restart-WebItem 'IIS:\Sites\ApiSite'
采集关键计数器(logman):
logman create counter iis-queue -f csv -o C:\logs\iis-queue -si 00:00:10 -v mmddhhmm -cf counters.txt
logman start iis-queue
`counters.txt` 示例:
\HTTP Service Request Queues(apppool_ApiPool)\CurrentQueueSize
\HTTP Service Request Queues(apppool_ApiPool)\Requests Rejected
\Web Service(_Total)\Current Connections
\Process(w3wp)\% Processor Time