Windows Server IIS 的应用程序池每隔一段时间自动回收,直接后果就是用户会话全丢、购物车清空、后台登录状态掉线。这个问题在生产环境里非常普遍,但很多人第一反应是关掉回收——这其实是把双刃剑。真正要解决的是“回收”与“会话保持”之间的矛盾,而不是简单粗暴地禁用机制。
应用程序池回收本身是 IIS 的一种自我保护设计。长时间运行的 Web 应用会积累内存碎片、未释放的句柄、线程泄漏等问题,定期回收能让工作进程(w3wp.exe)重新启动,把这些潜在风险清零。默认情况下,IIS 每 1740 分钟(29 小时)就会触发一次定期回收,同时还有内存阈值回收、请求数上限回收等触发条件。问题在于,一旦工作进程重启,所有保存在进程内存中的 Session 数据、缓存、静态变量全部消失,对于使用 InProc 模式存放会话的应用来说,这就是灾难。
要彻底解决这个问题,必须从架构层面把“会话状态”与“工作进程生命周期”解耦。下面从几个核心维度展开,每个方案都有具体的配置方法和适用场景。
把会话状态移出进程内存最直接有效的方案是放弃 InProc 会话模式,改用 State Server 或 SQL Server 模式。这两种模式都把 Session 数据存储在独立于 IIS 工作进程的外部容器中,工作进程重启完全不影响会话。
State Server 是 Windows 自带的一个服务,名叫 ASP.NET State Service。启用方法:在“服务”中把该服务启动并设为自动,然后在 Web.config 中修改 sessionState 节点:
如果 State Server 和 IIS 在同一台机器,地址就用 127.0.0.1;如果是多台 Web 服务器做负载均衡,可以把 State Server 单独部署在一台机器上,所有 Web 服务器指向同一个 State Server。需要注意,State Server 本身也有内存上限,默认注册表里最大是 256MB,超过后也会丢失数据,要根据业务量调整。
SQL Server 模式更稳定,适合对会话可靠性要求极高的场景。配置方式类似:
使用 SQL Server 模式前,需要用 aspnet_regsql.exe 工具在数据库中创建存储会话的表和存储过程。命令行为:aspnet_regsql -S 服务器地址 -U 用户名 -P 密码 -ssadd -sstype p。这个方案的优点是会话数据持久化,即使数据库服务器重启也不会丢;缺点是每次请求都要读写数据库,性能开销比 State Server 大,高并发场景下需要数据库有足够的 IO 能力。
使用 Redis 或其他分布式缓存如果应用已经是分布式架构,或者计划向微服务演进,直接用 Redis 存放 Session 是更现代的选择。通过安装 Microsoft.Web.RedisSessionStateProvider 这个 NuGet 包,可以把 Session 无缝迁移到 Redis 上。Web.config 配置如下:
Redis 方案的优势是性能极高、支持高可用集群、数据可持久化,而且能跨语言跨平台共享会话。缺点是需要额外维护 Redis 集群,但相比它带来的稳定性提升,这个成本是值得的。另外要注意,存放在 Session 中的对象必须标记为 Serializable,否则序列化时会报错,这是从 InProc 迁移到外部存储时最常见的坑。
调整应用程序池回收策略即使把 Session 移出去了,也不意味着回收策略可以完全不管。频繁的回收仍然会导致正在处理的请求中断、用户看到错误页面。所以需要合理配置回收条件。
在 IIS 管理器里,选中对应的应用程序池,点击“高级设置”,找到“回收”区域。这里有多个关键参数:
“特定时间间隔(分钟)”——这就是定期回收的核心设置。如果应用本身很稳定,内存管理良好,可以把这个值调大,比如 0 表示禁用定期回收,或者设为 10080(一周)。但不要同时把其他回收条件也关掉。
“虚拟内存限制”和“专用内存限制”——这两个是内存阈值回收。专用内存限制一般设为物理内存的 60%-70% 比较合理,比如服务器有 16GB 内存,可以设 10GB 左右。虚拟内存限制通常设 0 禁用即可,因为虚拟内存超限往往意味着严重的内存泄漏,应该从代码层面解决而不是靠回收掩盖。
“请求数限制”——达到指定请求数后回收。这个参数在早期 IIS 版本里用得较多,现在一般设为 0 禁用,因为现代应用的内存泄漏问题比请求数累积更值得关注。
还有一个容易被忽略的设置是“禁用重叠回收”。默认情况下,IIS 回收时会先启动一个新的工作进程,等新进程准备好后再把流量切过去,旧进程等待处理完现有请求后关闭,这个过程叫“重叠回收”。如果应用不支持多实例同时运行(比如有单例资源争用),就需要勾选“禁用重叠回收”,但这样在回收期间会有短暂的服务不可用。更好的做法是让应用支持多实例,保持重叠回收开启。
处理回收时的请求中断即使重叠回收开启,旧进程在关闭前有一个“关闭时间限制”,默认是 90 秒。如果某些请求处理时间超过这个限制,就会被强制终止。这个值可以在应用程序池高级设置的“进程模型”里找到,根据业务最长请求时间适当调大,比如 300 秒。
另外,在应用层面可以处理 Application_End 事件,在 Global.asax 里做一些资源清理和日志记录,但不要在这里尝试做复杂的异步操作,因为进程关闭时 CLR 会限制执行时间。
负载均衡场景下的会话保持如果有多台 Web 服务器,即使单台的 Session 已经外部化了,还需要考虑用户请求被分发到不同服务器时能否正确读取会话。State Server 和 SQL Server 模式天然支持多服务器共享,因为所有服务器都指向同一个存储。但如果用的是 InProc 模式又不想改代码,可以在负载均衡器上启用“会话保持”(Session Affinity),也叫粘性会话,让同一个用户的请求始终路由到同一台服务器。但这只是权宜之计,一旦那台服务器回收或宕机,会话还是会丢。所以负载均衡环境里,强烈建议配合外部会话存储使用。
监控与预警配置完成后,还需要建立监控机制。重点关注应用程序池的回收事件,可以在 Windows 事件查看器的“系统”日志里看到来源为 WAS 的事件,事件 ID 5074 表示回收开始,5076 表示回收完成。通过设置事件触发任务或使用监控工具,可以在回收频繁发生时及时收到通知。同时监控 State Server 或 Redis 服务的健康状态,避免外部存储本身成为单点故障。
对于 SQL Server 模式的会话存储,要定期清理过期的会话记录。ASP.NET 默认会通过存储过程自动清理,但如果会话量特别大,建议检查清理作业是否正常运行,避免会话表无限膨胀影响性能。
归根结底,IIS 应用程序池回收是保障系统长期稳定运行的必要机制,不应该被视为需要消灭的敌人。正确的思路是让应用架构适应这种“进程随时可能重启”的云原生思维——把状态外置、让应用无状态化。这样不仅解决了会话中断的问题,也为后续的水平扩展、容器化部署打下了基础。
