上一篇 下一篇 分享链接 返回 返回顶部

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

发布人:Minchunlin 发布时间:2025-08-31 09:55 阅读量:750


凌晨 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),设置项名称相同或高度一致,下文同用。

现象与基本判断

  1. 访问日志可见间歇性 503(Service Unavailable),平均响应时间上升。
  2. 性能计数器 `HTTP Service Request Queues/CurrentQueueSize` 瞬时飙升。
  3.  `Requests Rejected` 增加,Event Viewer 偶见 WAS 警告(池响应变慢)。
  4. 业务特征:突发并发 + 少量慢连接(上传、弱网)。

推断:

  1. 应用程序池的队列长度(queueLength)偏小,突发时溢出。
  2. 连接超时(connectionTimeout)、最小传输速率(minBytesPerSecond)等参数未针对弱网与慢请求做平衡,导致资源被“慢连接”占用过久。

背景图谱:一次请求在 IIS 内部怎么走?

TCP 连接 → HTTP.sys(内核态队列)→ 应用程序池队列(按池隔离) → w3wp 工作进程(用户态)→ ASP.NET 管道/业务代码 → 响应

  1. 队列溢出会直接 503;
  2. 连接超时既包括客户端空闲导致的断开,也包括服务器为了避免“慢速攻击/慢客户端”而设置的最小字节速率策略;
  3. 我们要做的是:让突发时队列能“兜住”请求,同时用合理的连接超时策略清走占坑不干活的连接。

调优目标

  1. 队列不丢:合理放大 `queueLength`,用“瓦桶”接住突发洪峰;
  2. 慢即断:对慢速/空闲连接设置“人性化但不纵容”的超时;
  3. 业务优先:把 CPU 用在有产出的请求上,避免被慢连和大文件长时间占满;
  4. 可观测:上线前后可量化验证,有回滚手段。

操作前的“三件套”

:: 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 里有两把“刀”:

  1. `connectionTimeout`:客户端在无数据传输时允许保持的最长时间(通常默认约 120s)。
  2. `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
目录结构
全文