基于反相代理的Web缓存加速(下)

发表于:2007-06-09来源:作者:点击数: 标签:
基于反相代理的Web缓存加速(下) 可缓存的动态页面设计 什么样的页面能够比较好的被缓存服务器缓存呢?如果返回内容的HTTP HEADER中有Last-Modified和Expires相关声明,比如: Last-Modified: Wed, 14 May 2003 13:06:17 GMTExpires: Fri, 16 Jun 2003 13:06:

基于反相代理的Web缓存加速(下)

可缓存的动态页面设计

什么样的页面能够比较好的被缓存服务器缓存呢?如果返回内容的HTTP HEADER中有"Last-Modified"和"Expires"相关声明,比如:

Last-Modified: Wed, 14 May 2003 13:06:17 GMT

Expires: Fri, 16 Jun 2003 13:06:17 GMT


前端缓存服务器在期间会将生成的页面缓存在本地:硬盘或者内存中,直至上述页面过期。

因此,一个可缓存的页面:

  • 页面必须包含Last-Modified:标记

    一般纯静态页面本身都会有Last-Modified信息,动态页面需要通过函数强制加上,比如在PHP中:

    // always modified now
    
    header("Last-Modified: " . gmdate("D, d M Y H:i:s")
    
    . " GMT");


  • 必须有Expires或Cache-Control: max-age标记设置页面的过期时间:

    对于静态页面,通过apache的mod_expires根据页面的MIME类型设置缓存周期:比如图片缺省是1个月,HTML页面缺省是2天等。

    <IfModule mod_expires.c> 
    
        ExpiresActive on
    
        ExpiresByType image/gif "access plus 1 month"
    
        ExpiresByType text/css "now plus 2 day"
    
        ExpiresDefault "now plus 1 day"
    
    </IfModule>


    对于动态页面,则可以直接通过写入HTTP返回的头信息,比如对于新闻首页index.php可以是20分钟,而对于具体的一条新闻页面可能是1天后过期。比如在php中加入了1个月后过期:

    // Expires one month later<br>
    
    header("Expires: " .gmdate ("D, d M Y H:i:s", time()
    
    + 3600 * 24 * 30). " GMT");


  • 如果服务器端有基于HTTP的认证,必须有Cache-Control: public标记,允许前台ASP应用的缓存改造 首先在公用的包含文件中(比如include.asp)加入以下公用函数:

  • <%
    
    ' Set Expires Header in minutes
    
    Function SetExpiresHeader(ByVal minutes) 
    
        ' set Page Last-Modified Header:
    
        ' Converts date (19991022 11:08:38) to http form (Fri, 22 Oct 1999 12:08:38 GMT)
    
        Response.AddHeader "Last-Modified", DateToHTTPDate(Now())
    
        
    
        ' The Page Expires in Minutes
    
        Response.Expires = minutes
    
        
    
        ' Set cache control to externel applications
    
        Response.CacheControl = "public"
    
    End Function 
    
    ' Converts date (19991022 11:08:38) to http form (Fri, 22 Oct 1999 12:08:38 GMT)
    
    Function DateToHTTPDate(ByVal OleDATE)
    
      Const GMTdiff = #08:00:00#
    
      OleDATE = OleDATE - GMTdiff
    
      DateToHTTPDate = engWeekDayName(OleDATE) & _
    
        ", " & Right("0" & Day(OleDATE),2) & " " & engMonthName(OleDATE) & _
    
        " " & Year(OleDATE) & " " & Right("0" & Hour(OleDATE),2) & _
    
        ":" & Right("0" & Minute(OleDATE),2) & ":" & Right("0" & Second(OleDATE),2) & " GMT"
    
    End Function 
    
    Function engWeekDayName(dt)
    
        Dim Out
    
        Select Case WeekDay(dt,1)
    
            Case 1:Out="Sun"
    
            Case 2:Out="Mon"
    
            Case 3:Out="Tue"
    
            Case 4:Out="Wed"
    
            Case 5:Out="Thu"
    
            Case 6:Out="Fri"
    
            Case 7:Out="Sat"
    
        End Select
    
        engWeekDayName = Out
    
    End Function
    
    Function engMonthName(dt)
    
        Dim Out
    
        Select Case Month(dt)
    
            Case 1:Out="Jan"
    
            Case 2:Out="Feb"
    
            Case 3:Out="Mar"
    
            Case 4:Out="Apr"
    
            Case 5:Out="May"
    
            Case 6:Out="Jun"
    
            Case 7:Out="Jul"
    
            Case 8:Out="Aug"
    
            Case 9:Out="Sep"
    
            Case 10:Out="Oct"
    
            Case 11:Out="Nov"
    
            Case 12:Out="Dec"
    
        End Select
    
        engMonthName = Out
    
    End Function
    
    %>


    然后在具体的页面中,比如index.asp和news.asp的“最上面”加入以下代码:HTTP Header

    <!--#include file="../include.asp"-->
    
    <%


    '页面将被设置20分钟后过期

    SetExpiresHeader(20)
    
    %>


    应用的缓存兼容性设计

    经过代理以后,由于在客户端和服务之间增加了中间层,因此服务器无法直接拿到客户端的IP,服务器端应用也无法直接通过转发请求的地址返回给客户端。但是

    在转发请求的HTTD头信息中,增加了HTTP_X_FORWARDED_????信息。用以跟踪原有的客户端IP地址和原来客户端请求的服务器地址:

    下面是2个例子,用于说明缓存兼容性应用的设计原则:

    '对于一个需要服务器名的地址的ASP应用:不要直接引用HTTP_HOST/SERVER_NAME,判断一下是否有HTTP_X_FORWARDED_SERVER

    function getHostName ()        
    
    	  dim hostName as String = ""        
    
    	   hostName = Request.ServerVariables("HTTP_HOST")        
    
    	   if not isDBNull(Request.ServerVariables("HTTP_X_FORWARDED_HOST")) then              
    
    	      if len(trim(Request.ServerVariables("HTTP_X_FORWARDED_HOST"))) > 0 then                
    
    		     hostName = Request.ServerVariables("HTTP_X_FORWARDED_HOST")          
    
    	       end if        
    
    	    end if        
    
    	    return hostNmae    
    
    	end function


    对于一个需要记录客户端IP的PHP应用:不要直接引用REMOTE_ADDR,而是要使用HTTP_X_FORWARDED_FOR,

    function getUserIP (){       
    
    	    $user_ip = $_SERVER["REMOTE_ADDR"];        
    
    	    if ($_SERVER["HTTP_X_FORWARDED_FOR"]) {            
    
    	       $user_ip = $_SERVER["HTTP_X_FORWARDED_FOR"];       
    
    	    }   
    
    	}


    注意:HTTP_X_FORWARDED_FOR如果经过了多个中间代理服务器,有何能是逗号分割的多个地址,比如:200.28.7.155,200.10.225.77 unknown,219.101.137.3

    因此在很多旧的数据库设计中(比如BBS)往往用来记录客户端地址的字段被设置成20个字节就显得过小了。

    经常见到类似以下的错误信息:

    Microsoft JET Database Engine
    
    错误 '80040e57'
    
    字段太小而不能接受所要添加的数据的数量。试着插入或粘贴较少的数据。
    
    /inc/char.asp,行236


    原因就是在设计客户端访问地址时,相关用户IP字段大小最好要设计到50个字节以上,当然经过3层以上代理的几率也非常小。

    如何检查目前站点页面的可缓存性(Cacheablility)呢?可以参考以下2个站点上的工具:

    http://www.ircache.net/cgi-bin/cacheability.py

    附:SQUID性能测试试验

    phpMan.php是一个基于php的man page server,每个man

    page需要调用后台的man命令和很多页面格式化工具,系统负载比较高,提供了Cache

    Friendly的URL,以下是针对同样的页面的性能测试资料:

    测试环境:Redhat 8 on Cyrix 266 / 192M Mem

    测试程序:使用apache的ab(apache benchmark):

    测试条件:请求50次,并发50个连接

    测试项目:直接通过apache 1.3 (80端口) vs squid 2.5(8000端口:加速80端口)

    测试1:无CACHE的80端口动态输出:

    ab -n 100 -c 10 http://www.chedong.com:81/phpMan.php/man/kill/1
    
    This is ApacheBench, Version 1.3d <$Revision: 1.2 $> apache-1.3
    
    Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd,
    
    http://www.zeustech.net/
    
    Copyright (c) 1998-2001 The Apache Group, http://www.apache.org/
    
    
    
    Benchmarking localhost (be patient).....done
    
    Server Software:       
    
    Apache/1.3.23                                     
    
    Server Hostname:        localhost
    
    Server
    
    Port:           
    
    80
    
    
    
    Document Path:         
    
    /phpMan.php/man/kill/1
    
    Document Length:        4655 bytes
    
    
    
    Concurrency Level:      5
    
    Time taken for tests:   63.164 seconds
    
    Complete requests:      50
    
    Failed requests:        0
    
    Broken pipe errors:     0
    
    Total transferred:      245900 bytes
    
    HTML transferred:       232750 bytes
    
    Requests per second:    0.79 [#/sec] (mean)
    
    Time per request:       6316.40 [ms]
    
    (mean)
    
    Time per request:       1263.28 [ms]
    
    (mean, across all concurrent requests)
    
    Transfer rate:         
    
    3.89 [Kbytes/sec] received
    
    
    
    Connnection Times (ms)
    
                 
    
    min  mean[+/-sd] median   max
    
    Connect:        0   
    
    29  106.1      0   553
    
    Processing:  2942  6016
    
    1845.4   6227 10796
    
    
    
    Waiting:    
    
    2941  5999 1850.7   6226 10795
    
    
    
    Total:      
    
    2942  6045 1825.9   6227 10796
    
    
    
    Percentage of the requests served within a certain time (ms)
    
      50%   6227
    
      66%   7069
    
      75%   7190
    
      80%   7474
    
      90%   8195
    
      95%   8898
    
      98%   9721
    
      99%  10796
    
     100%  10796 (last request)


    测试2:SQUID缓存输出

    /home/apache/bin/ab -n50 -c5
    
    "http://localhost:8000/phpMan.php/man/kill/1"
    
    This is ApacheBench, Version 1.3d <$Revision: 1.2 $> apache-1.3
    
    Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd,
    
    http://www.zeustech.net/
    
    Copyright (c) 1998-2001 The Apache Group, http://www.apache.org/
    
    
    
    Benchmarking localhost (be patient).....done
    
    Server Software:       
    
    Apache/1.3.23                                     
    
    Server Hostname:        localhost
    
    Server
    
    Port:           
    
    8000
    
    
    
    Document Path:         
    
    /phpMan.php/man/kill/1
    
    Document Length:        4655 bytes
    
    
    
    Concurrency Level:      5
    
    Time taken for tests:   4.265 seconds
    
    Complete requests:      50
    
    Failed requests:        0
    
    Broken pipe errors:     0
    
    Total transferred:      248043 bytes
    
    HTML transferred:       232750 bytes
    
    Requests per second:    11.72 [#/sec] (mean)
    
    Time per request:       426.50 [ms] (mean)
    
    Time per request:       85.30 [ms] (mean,
    
    across all concurrent requests)
    
    Transfer rate:         
    
    58.16 [Kbytes/sec] received
    
    
    
    Connnection Times (ms)
    
                 
    
    min  mean[+/-sd] median   max
    
    Connect:       
    
    0     1   
    
    9.5      0    68
    
    Processing:    
    
    7    83  537.4     
    
    7  3808
    
    
    
    Waiting:       
    
    5    81  529.1     
    
    6  3748
    
    
    
    Total:         
    
    7    84  547.0     
    
    7  3876
    
    
    
    Percentage of the requests served within a certain time (ms)
    
      50%      7
    
      66%      7
    
      75%      7
    
      80%      7
    
      90%      7
    
      95%      7
    
      98%      8
    
      99%   3876
    
     100%   3876 (last request)


    结论:No Cache / Cache = 6045 / 84 = 70

    结论:对于可能被缓存请求的页面,服务器速度可以有2个数量级的提高,因为SQUID是把缓存页面放在内存里的(因此几乎没有硬盘I/O操作)。

    小节:

  • 大访问量的网站应尽可能将动态网页生成静态页面作为缓存发布,甚至对于搜索引擎这样的动态应用来说,缓存机制也是非常非常重要的。

  • 在动态页面中利用HTTP Header定义缓存更新策略。

  • 利用缓存服务器获得额外的配置和安全

  • 日志非常重要:SQUID日志缺省不支持COMBINED日志,但对于需要REFERER日志的这个补丁非常重要:http://www.squid-cache.org/mail-archive/squid-dev/200301/0164.html
  • 原文转自:http://www.ltesting.net