IIS 重叠回收可能导致全局的 Application_Start 函数“不触发”

这两天在修改单位网站的 .NET 代码,其中有一个需求是这样实现的:

网站的告警信息以及每个告警对应的已读用户是保存在一个内存对象中的。

IIS 每天晚上会做定时的应用程序回收。

所以要在应用程序终止(Application_End)前将告警数据序列化导出成 Cache 文件保存(SaveCache);

然后在新的应用程序启动(Application_Start)后从 Cache 文件中读取告警数据,反序列化回来(LoadCache)。

在测试机器上这一系列代码是运行正常的,不论是 IIS 的定时回收,还是手工对 IIS 应用程序池中的进程进行回收,系统都会自动调用 Global.asax 文件中的 Application_End 函数与 Application_Start 函数。然后都能看到 Cache 文件生成,刷新网页后 Cache 中存储的数据也能正常地被读取展示出来。

而当我把代码放到在线服务器上测试时,每次手工回收应用程序池,Cache 文件是被刷新了,但是重启后的网页却无法展示 Cache 中的正确内容。查看网站日志,也只看到了 Application_End、 SaveCache,却没有 Application_Start、 LoadCache 的记录。

在线机与测试机的区别,一是在线机是双机群集,测试机是单机;二是在线机实时都有用户访问,测试机只有我自己在访问,访问量不一样。

所以我一开始怀疑是不是由于群集的原因导致 Application_Start 不触发,但是在网上查了半天也没任何头绪。

再认真看一下在线机的日志,发现每次回收应用程序时发生的 Application_End 输出之后,尽管之后没有 Application_Start 记录,但很快地几十毫秒内就有下一个请求的记录。而在测试机的日志中,Application_End 之后要等到我刷新网页,才会有 Application_Start 的日志,然后接着才是刷新请求的日志。所以当时我有一个一闪而过的念头,这是不是跟多进程有关系?但我的日志中没有输出进程号,暂时无法判断。而且当时我也看了应用程序池的高级配置,“最大工作进程数”的值是1,不应该有多个处理进程啊。

直到我在高级配置中看到一个可疑选项 “禁用重叠回收”。

配置项里的官方说明不清不楚,这里直接摘抄网上的说明:

重叠回收(Overlapped Recycling),指的是当回收的时候,原来的进程继续处理正在处理的请求,同时一个新的进程被创建来处理新的 Web 请求。新进程在就旧进程结束之前就启动了,后续的 Web 请求都由新进程处理。这种机制可以避免延迟,因为旧进程可以继续接受请求直到新进程初始化完成。这也是 IIS 的默认值。如果禁用这种回收方式,则新进程必须等待旧进程处理完之前的请求并退出后才能被启动,然后才能开始处理新的 Web 请求。这会导致回收时存在一小段无法响应的时间。

所以,在线机上 Application_Start “不被触发”的原因找到了!实际上,它是触发了的,但是由于旧进程未退出,日志文件被旧进程占用,所以新进程的 Application_Start 没办法写入日志。而且在旧进程未退出,新进程已启动的这个时间点,Cache 文件还没被旧进程的 SaveCache 更新(甚至可能 Cache 文件都未生成),所以这个时候新进程的 LoadCache 读到的 Cache 是更早期的数据(甚至可能为空)。这就导致了我在上面描述的状况:回收应用程序时,Cache 文件被更新了,但网页无法正常显示回收前的状态,日志中有 SaveCache 的记录,却没有 LoadCache 的记录。

测试机由于访问量太少,即使默认开启了重叠回收,在实际回收的过程中,并没有发生新旧进程的重叠。

既然知道了原因,那么针对这种现象,有两种解法:

1、直截了当将“禁用重叠回收”选项设置为 True。这样新进程就必须等待旧进程退出后才被创建,Cache 文件、日志文件就能按顺序被读写了。

2、如果更希望保留“重叠回收”来消弭新旧进程切换之间的无响应片刻。那就只能修改读写 Cache 文件和日志文件的逻辑了。比如 Cache 文件可以定时刷新,不要等到旧进程回收了才写一次;比如日志文件可以改成写完就关闭,不要长时间独占,或者改成每个进程写一个日志文件。

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top