演练:IIS 7.0 输出缓存

作者:Saad Ladki

Web 内容可以分为两个类别:静态内容和动态内容。 静态内容不会因请求而异。 返回到 Web 浏览器的内容始终相同。 静态内容的示例包括 HTML 文件、JPG 或 GIF 文件。

另一个是动态内容。 这是针对每个请求的动态内容更改生成的响应。 示例包括 ASP.NET 或 PHP 内容。

这两个类别之间有很大的范围,其中包括半动态内容。 想象一下执行数据库查询的动态 ASP.NET 页。 如果基础数据库表很少更改,则没有理由对每个请求执行此查询。

IIS 输出缓存功能面向半动态内容。 它允许缓存动态请求的静态响应,并获得巨大的可伸缩性。

先决条件

对于本演练,需要在 Windows® Vista SP1 或更高版本或者 Windows® Server® 2008 Beta 3 或更高版本上使用 IIS 7.0 或更高版本。 本演练还使用 ASP.NET 2.0,必须在 IIS 安装程序中将其安装为可选组件。

演练概述

在以下演练中,我们使用 IIS 扩展性接口向 JPG 文件添加特定于语言的版权消息。

首先,我们通过添加托管处理程序来执行此操作。 然而,在每个 JPG 文件中动态插入版权消息是有代价的,因为现在必须为每个 JPG 文件执行托管代码。

接下来,我们将安装一个 IIS 性能分析工具 WCAT 来测量 JPG 版权处理程序的吞吐量。

然后,我们将添加输出缓存以恢复通过添加版权处理程序而导致的性能下降。

  1. %systemroot%\inetpub\wwwroot 目录下创建名为“pictures”的目录。 在提升的命令 shell 中执行以下命令:

    md %systemdrive%\inetpub\wwwroot\pictures
    
  2. 将一些数字图片(本演练假定它们为 JPG 文件)复制到新的图片目录。

    注意

    由于 Windows Server 2008 上的 Internet Explorer 安全设置较高,你可能会收到一个安全对话框,告知你网站被阻止。 若要下载 IIS 壁纸,请将 wallpaper.iis7.org 添加到受信任的站点列表中。

  3. 使用 appcmd 命令行工具创建应用程序。

    %windir%\system32\inetsrv\appcmd add app -site.name:"Default Web Site" 
    
        -path:/pictures -physicalPath:%systemdrive%\inetpub\wwwroot\pictures
    
  4. 在 pictures 目录下创建目录 App_Code:

    md %systemdrive%\inetpub\wwwroot\pictures\App_Code
    
  5. 打开记事本并将以下代码粘贴到其中。

    using System;
    using System.Web;
    using System.Drawing;
    using System.Drawing.Imaging;
              
    namespace IIS7Demos
    {
        public class imageCopyrightHandler : IHttpHandler
        {
            public void ProcessRequest(HttpContext context)
            {
                string message = "Copyright © IIS 7.0 Team";
                try            {
                    string languageHeader;
             languageHeader = context.Request.Headers["Accept-Language"].Substring(0,2).ToUpper();
                    switch (languageHeader)
                    {
                        case ("DE"):
                            message = "IIS 7.0 Team - Alle Rechte vorbehalten";
                            break;
                        case ("ES"):
                            message = "Marca Registrada IIS 7.0 Team";
                            break;
                        default:
                            break;
                    }
                }
                catch 
               { 
                    // if something fails, e.g. no Language-Accept header, we go with the english message 
               }
                InsertCopyrightMessage (   context, 
                                message, 
                                "yellow"                            
                            );
            }
            void InsertCopyrightMessage(
                                HttpContext context, 
                                string message, 
                                string color
                             )
            {
                try 
                {
                    // get physical path of request 
                    string strPath = context.Request.PhysicalPath;
                    // load as bitmap 
                    Bitmap jpgFile = new Bitmap(strPath);
                    // add copyright message 
                    Graphics g = Graphics.FromImage(jpgFile);
                    Font f = new Font("Arial", 20, GraphicsUnit.Pixel);
                    SolidBrush sb = new SolidBrush(Color.FromName(color));
                    // write copyright message to bitmap 
                    g.DrawString(   message, 
                                    f, 
                                    sb, 
                                    5, 
                                    jpgFile.Height - f.Height - 5
                                );
                    f.Dispose();
                    g.Dispose();
    
                    // save it to response stream 
                    jpgFile.Save(   context.Response.OutputStream, 
                                    System.Drawing.Imaging.ImageFormat.Jpeg
                                );
                    jpgFile.Dispose();
                }
                catch (Exception e)
                {
                    context.Response.Write(e.Message);
                }
            }
              
            public bool IsReusable
            {
                get { return true; }
            }
        }
    }
    

    将文件另存为 %systemdrive%\inetpub\wwwroot\pictures\App\_Code\imageCopyrightHandler.cs

  6. 创建在请求 JPG 文件时执行此代码的处理程序:

    %windir%\system32\inetsrv\appcmd set config /section:system.webServer/handlers 
    
        /+[name='imageCopyrightHandler-Integrated',path='*.jpg',
    
        verb='GET,HEAD',type='IIS7Demos.imageCopyrightHandler',preCondition='integratedMode']
    
  7. 我们还必须启用目录浏览,因为还没有默认文档:

    %windir%\system32\inetsrv\appcmd set config "Default Web Site/pictures" 
    
        -section:directoryBrowse -enabled:true
    
  8. 通过在 Internet Explorer 地址栏中键入以下内容来浏览到图片应用程序:http://localhost/pictures。 单击 IIS 目录列表中指向 JPG 文件的链接。 应会看到带有插入的版权消息的 JPG 图像。

  9. 查看代码。 你会看到版权消息取决于浏览器发送的“Accept-Language”标头。 如果已安装德语版本的 Microsoft Server 2008,则会看到版权消息“IIS 7.0 Team - Alle Rechte vorbehalten”;如果有西班牙语版本,则会看到“Marca Registrada IIS 7.0 Team”。 在所有其他情况下,版权消息将为“Copyright © IIS 7.0 Team”。 测试此代码的一种方法是更改 Internet Explorer 发送的“Accept-Language”标头:

    • 打开“Internet Explorer”。
    • 打开“工具”菜单,然后单击“Internet 选项”。
    • 单击“语言”按钮。
    • 单击“添加…”按钮,为西班牙语添加“es”,或为德语添加“de”。
    • 通过“上移”按钮将新语言移动到列表顶部。
    • 浏览到 http://localhost/pictures/<your_jpg_file>.jpg。 版权消息已更改为你配置的语言。
    • 不要忘记返回到“语言”对话框并重置 -- 否则,你可能想知道以后为什么获得西班牙语或德语网页。

第 II 部分 - imageCopyrightHandler 的性能测试

JPG 版权处理程序工作后,必须确定代码的速度。 安装 IIS 6.0 资源工具包工具以运行性能测试:

  1. 下载 IIS 6.0 资源工具包工具并安装它们。 进行自定义安装,然后只安装 Web 容量分析工具 (WCAT)。 WCAT 是唯一需要执行性能测试的 IIS 6.0 资源工具包工具的功能。

    注意

    由于 Windows Server 2008 上的 Internet Explorer 安全设置较高,你可能会收到一个安全对话框,告知你网站被阻止。 若要下载 IIS 6.0 资源工具包,请将 *.microsoft.com 添加到受信任的站点列表中。

  2. 创建名为 PERFTEST 的目录,例如:

    md %systemdrive%\perftest
    
  3. WCAT 控制器需要三个输入文件:

    • 一个脚本文件,告知 WCAT 要请求的 URL。 每个 URL 获取唯一的 ClassID
    • 一个分发文件,告知 WCAT 如何跨脚本文件中指定的 URL 分发请求
    • 一个配置文件,配置特定性能运行的参数,例如测试持续时间、要模拟的 HTTP 客户端数等。

    脚本文件

    在 perftest 目录中创建名为 script.cfg 的新文件,并将以下内容粘贴到其中:

    NEW TRANSACTION
        classId = 1
        NEW REQUEST HTTP
            Verb = "GET"
            URL = "http://localhost/pictures/<your image name>.JPG"
    NEW TRANSACTION
        classId = 2
        NEW REQUEST HTTP
            Verb = "GET"
            URL = "http://localhost/pictures/<your image name>.JPG"
    

    注意

    将<映像名称>条目替换为 JPG 文件的名称。 如果有更多 JPG 文件,则可以添加新的事务。 请确保为每个事务提供新的 ClassID。

    分发文件

    分发文件告知 WCAT 它应该如何权衡请求。 对于上面的两个 URL,我们进行了 50/50 分发。 每个 ClassID 请求 50% 的时间。

    在 perftest 目录中创建名为 %systemdrive%\perftest\distribution.cfg 的文件,并将以下内容粘贴到其中:

    1 50
    2 50
    

    配置文件

    下面是测试的建议参数:

    • 持续时间:30 秒
    • 预热:5 秒
    • 冷却时间:5 秒
    • 模拟 Http 客户端:20

    在 perftest 目录中创建名为 config.cfg 的文件,并将以下内容粘贴到其中:

    Warmuptime 5s
    Duration 30s
    CooldownTime 5s
    NumClientMachines 1
    NumClientThreads 20
    
  4. 通过执行以下命令启动控制器:

    Cd \perftest
    "%programfiles%\IIS Resources\WCAT Controller\wcctl" 
        -c config.cfg -s script.cfg -d distribution.cfg -a localhost
    

    连接所有客户端后,性能测试就会启动。

  5. 由于我们只有一个客户端,请打开另一个提升的命令 shel 并运行以下命令:

    "%programfiles%\IIS Resources\WCAT Client\wcclient.exe" localhost
    

    若要对更多客户端执行此操作,请将 config.cfg 中的 NumClientMachines 设置为更高的数字,并通过 wcclient 命令将客户端连接到控制器,方法是指定控制器计算机的名称。

    示例:wcclient MyPerfTestControllerMachine

    注意

    如果在 64 位版本的 Windows 上执行此操作,WCAT 安装在“program files (x86)”目录中,并且必须使用 %programfiles(x86)% 启动 WCAT。

  6. 下面是第一次运行的结果:

    ########################################################################
    WCAT Performance Statistics_________________________________
    Server                      :      localhost      ()
    #Transactions               :              3      (HTTP/1.1)
    Total Async Sockets         :             20      (5 WCAT Pool Threads)
    Total Elapsed Time          :             30 Secs (0 Hrs,0 Mins,30 Secs)
    Current Connections         :             20
    Total Connection Attempts   :            436      (   14/Sec)
    Total Connect Errors        :              0      (    0/Sec)
    Total Success Connections   :            436      (   14/Sec)
    Total Consec. Connect Errors:              0      (    0/Sec)
    Total Bytes                 :       32301100      ( 1051 KB/Sec)
    Total Bytes Written         :          32264      (    1 KB/Sec)
    Total Bytes Read            :       32268836      ( 1050 KB/Sec)
    Total Requests              :            436      (   14/Sec)
    Total Responses             :            436      (   14/Sec)
    Total Socket Reads          :           6976      (  232/Sec)
    Total Socket Writes         :            436      (   14/Sec)
    Total Parse Errors          :              0      (    0/Sec)
    Total Socket Errors         :              0      (    0/Sec)
    Total I/O Errors            :              0      (    0/Sec)
    Total Internal Errors       :              0      (    0/Sec)
    Total Time Outs             :              0      (    0/Sec)
    Total 200 OK                :            436      (   14/Sec)
    Total 30X Redirect          :              0      (    0/Sec)
    Total 304 Not Modified      :              0      (    0/Sec)
    Total 404 Not Found         :              0      (    0/Sec)
    Total 500 Server Error      :              0      (    0/Sec)
    Total Bad Status            :              0      (    0/Sec)
    Min. Connect Time           :              0 MS
    Avg. Connect Time           :              0 MS
    Max. Connect Time           :             16 MS
    Min. Resp Time (1st Byte)   :           1281 MS
    Avg. Resp Time (1st Byte)   :           1371 MS
    Max. Resp Time (1st Byte)   :           1578 MS
    Min. Response Time (Last)   :           1281 MS
    Avg. Response Time (Last)   :           1373 MS
    Max. Response Time (Last)   :           1578 MS
    Current Outstanding Connects:              0      (   20 Max)
    Current Waitable Connects   :              0      (   20 Max)
    Total Asynchronous Connects :            531      (    1/Sec)
    Total Discarded Connects    :              0      (    0/Sec)
    ########################################################################
    

    要查看的重要数字是每秒请求数。 在这种情况下,我们每秒收到 14 个请求。

    需要注意的是,JPG 文件越大,你看到的请求就越少。 计算机可能受网络限制:IIS 将无法处理更多请求,因为网络中已充斥着你发送的数据。 可以在 200-300 KB 范围内看到 JPG 文件的最佳结果。

添加输出缓存

动态插入版权消息的代码相当慢。 对于 Web 服务器,每秒 14 个请求并不快。 IIS 的性能要好得多。 只需创建一个缓存策略,该策略会将带有 JPG 扩展的 URL 放入内核模式缓存中。 下面是添加缓存策略的方式:

通过 IIS 管理工具添加缓存策略

  1. 导航到“管理工具”,然后选择“Internet Information Services (IIS) 管理器”。
  2. 使用左侧的树视图导航到“图片”应用程序。
  3. 选择“输出缓存规则”菜单项。
  4. 在“操作”菜单中单击“添加…”。
  5. 将 JPG 添加为缓存的“文件扩展名”。
  6. 在“监视缓存的文件”部分选择“时间间隔”,并输入 00:00:10 作为时间间隔。
  7. 选中“标头”复选框并输入“Accept-Language”。

注意

输出缓存用户界面在 Windows Vista Service Pack 1 之前的版本中不可用。

通过命令行添加缓存策略

若要使用 appcmd 工具执行相同的过程,请输入以下命令:

%windir%\system32\inetsrv\appcmd set config "Default Web Site/pictures" 
    -section:caching /+profiles.[extension='.jpg',duration='00:00:10',
    policy='CacheForTimePeriod',varyByHeaders='Accept-Language']

重复性能运行,查看配置设置更改的内容和方式。

  1. 通过执行以下命令启动控制器:

    Cd \perftest
    "%programfiles%\IIS Resources\WCAT Controller\wcctl" 
        -c config.cfg -s script.cfg -d distribution.cfg -a localhost
    
  2. 使用以下命令启动客户端:

    "%programfiles%\IIS Resources\WCAT Client\wcclient.exe" localhost
    

    注意

    如果在 64 位版本的 Windows 上执行此操作,WCAT 安装在 program files (x86) 目录中,并且必须使用 %programfiles(x86)% 来启动 WCAT。

示例输出

########################################################################
WCAT Performance Statistics_________________________________
Server                      :      localhost      ()
#Transactions               :              3      (HTTP/1.1)
Total Async Sockets         :             20      (5 WCAT Pool Threads)
Total Elapsed Time          :             30 Secs (0 Hrs,0 Mins,30 Secs)
Current Connections         :             19
Total Connection Attempts   :          13020      (  434/Sec)
Total Connect Errors        :              0      (    0/Sec)
Total Success Connections   :          13019      (  433/Sec)
Total Consec. Connect Errors:              0      (    0/Sec)
Total Bytes                 :      958045737      (31186 KB/Sec)
Total Bytes Written         :         963406      (   31 KB/Sec)
Total Bytes Read            :      957082331      (31155 KB/Sec)
Total Requests              :          13019      (  433/Sec)
Total Responses             :          13019      (  433/Sec)
Total Socket Reads          :         258283      ( 8609/Sec)
Total Socket Writes         :          13019      (  433/Sec)
Total Parse Errors          :              0      (    0/Sec)
Total Socket Errors         :              0      (    0/Sec)
Total I/O Errors            :              0      (    0/Sec)
Total Internal Errors       :              0      (    0/Sec)
Total Time Outs             :              0      (    0/Sec)
Total 200 OK                :          13019      (  433/Sec)
Total 30X Redirect          :              0      (    0/Sec)
Total 304 Not Modified      :              0      (    0/Sec)
Total 404 Not Found         :              0      (    0/Sec)
Total 500 Server Error      :              0      (    0/Sec)
Total Bad Status            :              0      (    0/Sec)
Min. Connect Time           :              0 MS
Avg. Connect Time           :              0 MS
Max. Connect Time           :             63 MS
Min. Resp Time (1st Byte)   :              0 MS
Avg. Resp Time (1st Byte)   :             33 MS
Max. Resp Time (1st Byte)   :            125 MS
Min. Response Time (Last)   :              0 MS
Avg. Response Time (Last)   :             45 MS
Max. Response Time (Last)   :            141 MS
Current Outstanding Connects:              0      (   20 Max)
Current Waitable Connects   :              0      (   20 Max)
Total Asynchronous Connects :          14093      (  147/Sec)
Total Discarded Connects    :              0      (    0/Sec)
########################################################################

输出缓存高级主题

性能计数器

若要确定输出缓存中的性能,请查看“可靠性和性能监视器”中的输出缓存计数器。 有许多有趣的计数器。 下面是如何将“可靠性和性能监视器”与输出缓存结合使用的一个示例。

  1. 在 Windows Server 2008 上,通过“开始”菜单启动 PERFMON。 转到“管理工具”,然后单击“可靠性和性能监视器”。 在 Vista 上,可在控制面板中找到“管理工具”。
  2. 在右侧的树视图中选择“性能监视器”,然后单击工具栏中的大“+”号。
  3. 导航到“Web 服务缓存”计数器,然后单击它以打开。
  4. 添加“缓存的总 URI”计数器。
  5. 重新运行 WCAT 测试。

可以看到,缓存 URI 的数量根据在性能测试期间请求的项数而增加。

IIS 内核模式缓存规则

IIS 输出缓存支持两个缓存策略。 常规输出缓存策略利用驻留在 IIS 工作进程中的缓存。 另一个缓存策略是内核模式缓存策略,在这种情况下,缓存驻留在内核模式驱动程序 HTTP.SYS 中。

以内核模式缓存内容可以使网站运行得更快。 修改图片应用程序的配置,以使用内核模式缓存。 下面是当前配置的外观 (%systemdrive%\inetpub\wwwroot\pictures\web.config):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <directoryBrowse enabled="true" />
        <caching>
            <profiles>
                <add extension=".jpg" policy="CacheForTimePeriod" 
                duration="00:00:10" varyByHeaders="Accept-Language" />
            </profiles>
        </caching>                  
  </system.webServer>
</configuration>

现在,请将其更改为使用内核模式缓存:

  1. 打开 %systemdrive%\inetpub\wwwroot\pictures\web.config

  2. 更改设置。

    <caching>
        <profiles>
            <add extension=".jpg" policy="CacheForTimePeriod" 
                    duration="00:00:10" varyByHeaders="Accept-Language" />
        </profiles>
    </caching>
    

    更改为以下内容:

    <caching>
        <profiles>
                <add extension=".jpg" kernelCachePolicy="CacheForTimePeriod" 
            duration="00:00:10" />
            </profiles>
    </caching>
    

你会看到我们不再使用 varyByHeaders 属性。 这是因为 kernelModeCache 不支持用户模式输出缓存支持的某些功能。

内核模式输出缓存的限制

用户模式和内核模式输出缓存之间存在两个显著差异。

  • 内核模式输出缓存不支持必须在用户模式下运行的模块和功能,例如身份验证或授权。 示例:如果启用了基本身份验证或 Windows 身份验证等身份验证方案,则缓存策略将不起作用。 内容已提供,但未缓存。 有关如何确定内容是否已缓存,请参阅“缓存故障排除”。 有关响应在内核模式下为何无法缓存的更多详细信息,请参阅此知识库文章
  • 内核模式输出缓存支持 varyByHeaders 属性,但不支持 varyByQuerystring。

缓存故障排除

失败的请求事件缓冲 (FREB) 是确定请求是否已缓存的最佳方式。 FREB 告诉你为什么不缓存某些内容。 下面是 FREB 日志的示例。 在这种情况下,HTTPSYS_CACHEABLE 事件会告知你请求不会缓存,因为未启用内核模式缓存。

有关如何使用 FREB 的更多详细信息,请参阅使用 IIS 7 中的跟踪对失败的请求进行故障排除

使用以下命令找出在内核模式下缓存的内容:

netsh http show cachestate

缓存复杂情况

即使启用输出缓存,IIS 也不会立即缓存请求。 在 IIS 将请求视为“值得缓存”之前,必须多次请求它。 可以通过此 MSDN 文章中所述的 ServerRuntime 部分配置缓存价值。

确定缓存价值的两个属性是 frequentHitTimePeriod 和 frequentHitThreshold。 只有当可缓存 URL 在 <frequentHitTimePeriod> 内达到 <frequentHitThreshold> 个以上的请求时,才会缓存请求。

frequentHitTimePeriod 的默认设置为 10 秒。

frequentHitThreshold 的默认设置为 2。

在上面的示例中,我们将扩展名为 JPG 的所有文件放入输出缓存中。 这并不总是起作用,因为有时你想要更具选择性,并且只将特定文档放入输出缓存中。 下面是使用最常请求的页面(默认文档)执行此操作的方式:

  1. %systemdrive%\inetpub\wwwroot\pictures 目录中创建名为 default.aspx 的文件,并添加以下代码:

    <%=DateTime.Now%>
    
  2. 导航到“管理工具”,然后选择“Internet Information Services (IIS) 管理器”。

  3. 使用左侧的树视图导航到“图片”应用程序。

  4. 单击页面底部的“内容视图”。

  5. 选择默认文档,例如 default.aspx 页。

  6. 单击右侧“操作”菜单中的“切换到功能视图”。 现在,配置的每个设置将仅应用于默认文档。

  7. 打开“输出缓存规则”设置。

  8. 添加“.aspx”作为文件扩展名。

  9. 选择“内核模式缓存”,然后我们可以选择“时间间隔”并启用“监视缓存的文件”,输入 00:00:30 作为时间间隔。

  10. 使用“Internet Explorer”浏览到 http://localhost/pictures。 通过不断刷新页面(按 Ctrl+F5 以确保它不是来自浏览器缓存),可以看到时间在 30 秒内不会更改。

总结

对半动态内容使用 IIS 输出缓存功能可以改进网站。 你会看到性能和吞吐量容量显著改善。 简单的配置更改足以利用此功能。