
在我们的 Windows Server 2003 上具有一个已安装卷,我们正在将某个站点的路径指向该位置(例如,C:\Web)。当客户端仅使用完全限定域名(或虚拟目录)进行请求时,他们将收到一条 404 错误消息。不过,可在此页面上直接请求文件。当不包含文件名时为何收到一条 404 错误消息?
此非标准磁盘配置的不寻常特性即是问题所在,因为已安装磁盘的根分区由服务(如 IIS)的操作系统表示。
例如,考虑以下方案:
磁盘 1:20 GB NTFS 卷 (C:\)
磁盘 2:80 GB NTFS 卷
假设已单独使用磁盘 1 和路径 C:\Web 为所有 Web 内容设置了 Web 环境。为避免重复执行 Web 应用程序而产生额外的开销,您可能想扩展存储区。但是,您希望在达到此目的的同时,对 Web 应用程序代码做尽可能少的更改。
因此,您创建磁盘 2 配置并将其作为已创建的 C:\ 卷的一部分安装。在此情况下,您将重写此卷的根分区,该分区将位于另一卷上的另一个物理目录中。这可能令人感到迷惑,但对于许多 Web 管理员而言,这并非是一种不可能的配置。
言归正传:如果客户端在进行请求时未提供实际文件供 IIS 提取,IIS 将失败。在此方案中,客户端依据 IIS 服务器来提供基于默认文档的文档。默认文档列表位于虚拟目录“文档”选项卡中,它通常包含 default.htm、default.html、default.aspx 等项。当向 IIS 发出不包含特定文件的请求时,IIS 将尝试完成此“文档”列表并打开文件。当 IIS 调用某个特定 API - CreateFile 时,它会以独特的方式进行此操作。CreateFile 调用将尝试查找驱动器的根分区,然后将相应的文档附加到所请求的 Web 站点的路径中。
例如,假设“默认站点”具有以下配置:
| • | 路径:C:\inetpub\wwwroot |
| • | DefaultDoc:index.htm |
当向该站点发出请求且未指定文件时,IIS 将调用 API 并请求文件“\\?\inetpub\wwwroot\index.htm”。在大多数情况下,这是完全可以接受的。在此配置中则不然,您已在现有驱动器中安装了一个分区。IIS 6.0 通常会忽略这样一个事实,即典型分区的根分区将被隐藏,而当其为已安装卷时则不会被隐藏。在此特殊情况下,搜索根分区的操作会因 Windows Server 2003 中的磁盘行为而失败。
出现此问题的真正根本原因是:使根分区隐藏的已安装分区的默认行为导致了该失败。IIS 6.0(及早期版本)通常不会提供隐藏内容,因为这将违背隐藏文件的真正目的。而在根分区中则除外。IIS 将忽略标准根分区(如 C:、D: 等),并使隐藏根卷的正常操作系统行为有效。另一方面,已安装卷则不同,因为它们是重定向,IIS 不会忽略隐藏根分区这一事实。
要纠正此问题,可编写一个一次性脚本来更改已安装卷根分区的属性。也可编写一个 ISAPI 筛选器,以便在处理前重新映射请求。第一种方法(即更改属性)更为有效和高效,因为它不影响运行时或生产系统。其只执行一次即可。要更新文件属性,只需编写几行 C 代码,对其进行编译,然后执行。
我为您提供了一个可供使用的示例脚本,以便引导您正确执行操作。
#include "precomp.hxx"
extern "C" INT
__cdecl
wmain(
INT argc,
PWSTR argv[]
)
{
if (argc != 2)
{
printf("Usage: %S <mount-directory-name>\n", argv[0]);
return 1;
}
PWSTR pszDir = argv[1];
DWORD cchDir = wcslen(pszDir);
if (pszDir[cchDir - 1] != L'\\')
{
printf("The mount-directory-name should terminate with a \\\n");
return 1;
}
WCHAR pszVolumeName[50];
if (!GetVolumeNameForVolumeMountPoint(pszDir,
pszVolumeName,
sizeof(pszVolumeName)/sizeof(WCHAR)))
{
printf("The directory %S does not seem to be a root mount point\n", pszDir);
return 1;
}
if (!SetFileAttributes(pszDir,
FILE_ATTRIBUTE_NORMAL))
{
printf("SetFileAttributes failed - error %d\n", GetLastError());
return 1;
}
return 0;
}
如果您对第二种方法感兴趣,请阅读 IIS SDK(英文)。它将帮助您了解如何构建一个专门用于在将请求转交给 IIS 处理程序前对其进行修改的 ISAPI 筛选器。
我公司有一个相当大的共享 IIS 服务器。我们正尝试找到一些区域,我们可在其中简化日志文件的某些管理,因为处理 1000 个以上的日志文件有些繁琐。我们运行的是最近更新过的 Windows Server 2003。
Windows Server 2003 提供了一项可以使用的新功能 - 非常强大的“集中式二进制日志记录”(CBL)。
CBL 是 HTTP.sys(IIS 6.0 的一部分)的一部分。在 Windows Server 2003 中,除 ODBC 日志记录以外的所有日志记录均由 HTTP.sys 内核驱动程序来执行。这就意味着,IIS 6.0 将不再像在早期版本中那样,负责将每个请求的正确日志数据写入磁盘。CBL 是一种经过高度优化的日志格式,在此格式下,所有请求数据都被记录到一个二进制文件中。此未经格式化的二进制数据便于操作,不过,对像您这样的管理员来说还比较困难。实际上,您没有有效的方法来读取使用 CBL 创建的日志文件,除非您编写自己的 C++ 程序。
Log Parser 2.2 是一款非常好的命令行实用程序,可通过它来读取和格式化数据。有关将 Log Parser 用于 CBL 日志的详细信息,请观看我的网络广播:TechNet Webcast:IIS 6.0 中集中式二进制日志记录的输入与输出(英文)。
我为您提供的最好消息就是在 Service Pack 1 的 HTTP.sys 中实现了更新的功能。此功能将两种日志格式合并到了一起,以创建一种高性能但又易于管理的日志记录机制。它被称为“W3C 集中式日志记录”。此功能的关键所在是在类似于您所遇到的情况下使用了“集中式”日志文件,如果不使用此功能,将无法管理站点、目录和文件。
使用 W3C 集中式日志记录,管理员可准确获取一个于 W3SVC 目录中创建的日志文件。此文件符合 W3C 标准,并允许记录标准属性和扩展属性,如提供给 IIS 的 cookie 和 Win32 错误代码。CBL 用户不可以使用这些“扩展”属性。
在您所处情况下,切换到“W3C 集中式日志记录”将允许您按日、小时等(基于翻转机制)管理单一日志文件。这要比 IIS 6.0 的一个目录、多个文件的方法好得多。此外,无需使用任何专用工具(如 Log Parser 2.2)来查看数据。用您最喜欢的文本编辑器即可查看数据。但要注意,文件通常会很大,因此“记事本”编辑器可能无法满足需要。
遗憾的是,启用“W3C 集中式日志记录”所要进行的工作比在用户界面中启用一个复选框所要进行的工作要多一些。同 CBL 一样,“W3C 集中式日志记录”在 IIS 管理器中不可用,必须在配置数据库中直接对其进行编辑。要执行此操作,只需按如下所述内容修改 CentralW3CLoggingEnabled 配置数据库属性即可:
cscript adsutil.vbs set w3svc/CentralW3CLoggingEnabled 1
启用此值后,可重新启动 IIS 服务,重新启动后,现在 IIS 将使用一个带格式的文件(可通过文本编辑器读取)记录所有站点的所有请求。

图 1: 已启用“W3C 集中式日志记录”
在对我们的新 ASP.NET 应用程序进行压力测试时,开发人员说我们会看到性能得到了提高,因为他们使用了 ASP.NET 的输出缓存功能 (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspnet/html/asp03282002.asp) 将此动态请求存储在内核中。而根据我们的测试,情况似乎并非如此。我们启用 outputcache 后,仍是失败。我们丢失了什么?
新内核驱动程序 HTTP.sys 非常强大。此驱动程序可避免从内核模式到用户模式的必要转换,从而大大提高了性能 在许多情况下,速度最快可达用户模式缓存项的四倍。
问题的关键在于您是否成功遵循了与完成此工作相关的所有细节。在此答案中,我将尽量给出您可能会遗漏的可能细节。不过,您的问题可能具有多种原因。
第一步是确保 ASP.NET 应用程序遵循了可由内核缓存所必需的几个规则。在 IIS 6.0 文档和 Microsoft 知识库文章 817445 中都对这些规则进行了简要说明。
确认 ASP.NET 应用程序符合这些条件后,应确保已安装的所有过滤器具有相应的设置。ISAPI 过滤器专门用于修改向服务器发出的请求和由服务器发送的响应。此条件会使内核缓存出现问题,因为 IIS 从不处理请求。因此,为使内核缓存有效工作,管理员需要确保所安装的全部 IIS 6.0 的 ISAPI 过滤器的 FilterEnableCache 配置数据库属性都设置为 True(或非零)。
要查找当前为您的服务器或站点加载的 ISAPI 过滤器列表,请执行以下操作:
使用命令行实用程序 adsutil.vbs 或 IIS 6.0 Resource Kit Tools 实用程序 Metabase Explorer(见图 2)。

图 2:IIS Metabase Explorer 中的 FilterEnableCache
默认情况下,IIS 6.0 仅附带了两个 ISAPI 过滤器。ASP.NET 过滤器和 FrontPage ISAPI 过滤器。这两个过滤器均为支持“缓存”的过滤器,默认情况下,都将被正确启用。只有在以下两种情况下才会出现此问题:加载了基于 ISAPI 过滤器的自定义应用程序或编写了您自己的 ISAPI 过滤器。
如果 FilterEnableCache 设置为 0,则会关闭所有内核缓存。然而,只是将其设置为 1(非零)并不会立即打开缓存。要启用缓存并利用其优点,必须确保设置了此属性的过滤器未执行某些操作。如您所知,过滤器专门用于修改服务器的行为。它们根据向服务器发出的请求执行操作,从而达到此目的。所执行的操作是在分析 HTTP 标头时确定的。恰恰是这些操作可能会造成使过滤器可识别缓存的操作无效。例如,有些过滤器可能会修改所请求的实际 URL(/vdir1 被“重新映射”到 /vdir2)或可能监听以下通知“SF_NOTIFY_SEND_RAW_DATA”或“SF_NOTIFY_LOG”。如果未执行这些操作,则内核缓存将按计划工作。
总之,应了解您的服务器上加载了哪些过滤器以及它们执行了哪些操作。这有助于您更好地解决故障(如您所描述的故障)。
下一步将更多地涉及 ASP.NET 应用程序的开发。为了启用缓存,应将页面标记为可缓存,还应将 Web 应用程序设置为使用内核缓存。
要启用页面级 OutputCache,可在页面顶部添加以下指令:
<%@ outputcache duration="60" varybyparam="*" %>
启用此功能后,应能看出性能略有提高,因为 ASP.NET 将内部缓存数据,以便其他客户端能够更快速地对其进行检索。然而,这并不会启用内核缓存,仍需要内核模式到用户模式的转换。正如上文所述,这会使速度慢四倍。
要使 ASP.NET Web 应用程序将响应缓存到“内核中”,AppDomains 根应修改内容目录中的 Web.config 文件,如图 3 所示。

图 3:Web 配置文件修改
ASP.NET 具有一些必须遵守的特定规则(如表 1 所示)。
| 表 1:ASP.NET 缓存规则 | |
可缓存性 | ...需要为 Public (<%@ OutputCache Location="Any"%> |
附加缓存依赖关系 | 应“无” |
变化依据... | ...不应有 VaryByHeaders 或 VaryByParams (NONE) |
缓存字段 | 避免使用任何自定义缓存字段 |
验证 | 不执行验证回调 |
动词的使用 | 不应有 GET 以外的其他动词 |
无“存储”... | ...未设置。 |
过期 | ...设置为固定的时间间隔而非可变的时间间隔 |
如果遵守这些规则,ASP.NET 的 httpRuntime 将允许内核驱动程序 HTTP.sys 缓存内容。
有关 EnableKernelOutputCache 的详细信息,请参见 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt06.asp。
正如您所看到的,这不是一个“翻转开关”功能,因为动态应用程序通常在若干不同的层运行。应确保每个配置设置都是正确的以便利用此 IIS 6.0 和 ASP.NET 的绝妙功能。我希望这能帮助您解决问题!
请将您的问题提交给 IIS Insider。被选中的问题及其答案将在以后的 IIS Insider 专栏中公布。
有关以前月份的 IIS Insider 问题及答案列表,请单击此处。