http://www.tuicool.com/articles/EFfeu2
HTTP性能的问题与方案
一个最终用户访问一个网页,从浏览器发出请求,到接受请求,时间大体上消耗在了以下几个部分:
-
建立tcp/ip握手连接。
-
浏览器向服务器传送请求数据。
-
服务器处理数据。
-
服务器返回数据。
如果用户请求的资源很少改变,像js,css,图片之类的静态文件,如果每次用户的请求都需要占用服务器资源去处理,再如果 一个用户和服务器位于太平洋两岸,那么,时间就被浪费在了网络传输和服务器处理步骤上了。在这种情况下,应该使用cache。
像上图一样,在离用户最近的地方,增加一个缓存服务器,将不常修改的静态文件缓存起来,用户的请求就可以直接由缓存服务器来处理,而不需要再劳烦网站服务器了。
一般缓存服务器,代理服务器和逆向代理服务器在一起,即,一个Apache Httpd上,同时开启了缓存功能与代理功能。只是这台服务器从不同的功能角度,被称为不同的名字,有时叫缓存服务器,有时叫代理服务器。
Http缓存机制
http缓存是遵循http协议实现的。控制缓存行为的字段均在http header中。这些字段分别在几个方面控制着缓存:
-
有效期机制
-
重验证机制
-
共享机制
有效期机制
控制缓存有效期的有下面几个header,我必须将它们列出来。
头名 | 作用域 | 描述 |
Response | 原始服务器产生响应的时间,GMT格式。必须由服务器产生,代理不允许修改此值。 | |
Response |
每一级缓存发出cache时,跟Date头部相比,cache已经被使用的时间,以秒为单位。 1.1中Age必须出现在cache中。应有各级代理产生。 |
|
Expires | Response | 缓存过期时间,GMT格式。可由代理修改。 |
Cache-Control: max-age=xx | Request和Response |
在response中声明此缓存保持新鲜的生命时间。 在request中发送max-age=0,效果则同no-cache。 |
Cache-Control: no-cache:这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control: no-cache是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。
Cache-Control: no-store:这个才是响应不被缓存的意思。
Pragma: no-cache:跟Cache-Control: no-cache相同,Pragma: no-cache兼容http 1.0 ,Cache-Control: no-cache是http 1.1提供的。因此,Pragma: no-cache可以应用到http 1.0 和http 1.1,而Cache-Control: no-cache只能应用于http 1.1.
计算一个缓存是否有效,只需要计算缓存的使用期(cache age)和新鲜生存期(freshness lifetime)。缓存有效的判别式为:
isValid = cache_age < freshness_lifetime;
新鲜生存期比较好计算,应该为Cache-Control:max-age的值。如果没有Cache-Control:max-age的话,则使用Expires,如果都没有,则使用试探时算法(略)。计算式如下:
if (cache-control_max_age_set) fressness_lifetime = Cache-Control:max-age;
else if (Expires_set) freshness_lifetime = Expires - Date;
else 试探算法。
为何Expires比max-age给替代了?因为Expires有可能是各级代理服务器写入的,但Date是由原始服务器写入的,各个服务器的时钟可能存在差异,其准确度无法得到保证。
缓存使用期则是比较难计算的。 它不能简单的用当前时间去减原始服务器写入的Date ,原因是各个服务器的时钟差异,有的可能差几天。所以,它的计算依靠每一级代理服务器返回缓存时所带的age头部。对于离服务器最近的一级代理服务器,age头部的算法应该为:
age = (response_arrive_time - request_sent_time) + (now - response_arrived_time) - 修正值; //此计算并不精确,但误差在秒级。
那下一级代理的age算法应该为:
age = age_of_cache_returen_from_last_proxy + cache_time_saved_in_this_proxy;
递归下去,可以计算出每一级代理的age头部。
重验证机制
下面列出了几个重要的头部,还有一些没有列在其中。
头名 | 作用域 | 描述 |
Cache-Control: no-catch | Request和response | 请求必须对缓存进行重验证。有些服务器略有出路,把no-cache与no-store混淆。 |
Pragma: no-catch | Request和response | 请求必须对缓存进行重验证。为了向http1.0兼容。 |
Cache-Control: must-revalidate | response | 强制约束此cache在过期后,必须通过重新验证以后才可使用。 |
Last-Modified | Response | 返回的内容上次被修改的日期的long值,精确到秒。 |
If-Modified-Since | Request | 请求时带上过期缓存的Last-Modified值。 |
Response | 响应实体的标签。 | |
If-None-Match | Request | 请求时带上过期缓存的标签。 |
以上的几个头部,组成了cache的重验证机制。前3个头部是对cache强制认证的约束,后面4个则是实体认证时所需要检测的属性。
需要注意no-cache和must-revalidate的区别。no-cache表明缓存在被返回之前,必须向服务器冲验证是否被修改过。有时候,代理允许使用过期的cache,比如,服务器死机了,代理的cache虽然已经过期,但还是可以被使用。must-revalidate并不是要求cache一定要重验证,未过期的cache不会因它的存在而去服务器重新验证。must-revalidate是为了禁止这种行为。它要求过期的缓存一定要重验证通过后才能使用,如果服务器死机了,那无法进行重验证,过期的cache也就不能再被使用,用户收到504 gateway timout。 must-revalidate对有效期内的cache并无作用。
共享机制
缓存应该存在什么地方,则由下面的三个头部组成。
头名 | 作用域 | 描述 |
Cache-Control: public | Response | 共享缓存。缓存可被代理服务器存储。 |
Cache-Control: private | Response | 私有缓存,缓存不能被代理存储,可由浏览器存储。 |
Cache-Control: no-store | Request和Response | 不可存储缓存。 |
Cache的流程图
因为整个过程中存在很多的条件节点,时序图并不好表示出因果关系,所以还是用流程图更好一些。 我想了很久,才想出如何用一种更直观的方法来画出cache工作的流程图。
黄色线代表request的流程,蓝色线代表response的流程,红色线代表原始服务器处理流程。
Apache Httpd搭建Cache Server
软件需要
-
Tomcat 8
-
Apache Httpd 2.2
服务器开发
我制作一个图片展示页面。页面上展示4张图片,每张图片的响应都包含不同的cache header。这四张图片如下:
图片名 | public.jpg | private.jpg | noStore.jpg | noCache.jpg |
http header |
Cache-Control: public, max-age=600 Last-Modified:xxxxxx Etag:xxxxx |
Cache-Control: private, max-age=600 Last-Modified:xxxxxx Etag:xxxxx |
Cache-Control: no-store |
Cache-Control: no-cache Last-Modified:xxxxxx Etag:xxxxx |
为此,我可以专门写一个filter,来管理这些header。Last-Modified和ETag头会由tomcat的default servlet自动添加。我就不自己去处理了。此filter在doFilter方法上加了synchronized修饰,为了让请求按顺序执行,这样我们就可以看清楚每个请求和返回的header都有什么。我把header都打印出来了。
@WebFilter(urlPatterns={"*.jpg"})
public class CacheFilter implements Filter {
private int count = 0;
public void init(FilterConfig filterConfig) throws ServletException {}
public void destroy() {}
public synchronized void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String uri = req.getRequestURI();
System.out.println("["+count+++"]Accessing "+uri);
printRequestHeaders(req);
HttpServletResponse resp = (HttpServletResponse) response;
if (uri.endsWith("public.jpg")) {
resp.addHeader("Cache-Control", "max-age=600,public");
}
if (uri.endsWith("private.jpg")) {
resp.addHeader("Cache-Control", "max-age=600,private");
}
if (uri.endsWith("noStore.jpg")) {
resp.addHeader("Cache-Control", "no-store");
}
if (uri.endsWith("noCache.jpg")) {
resp.addHeader("Cache-Control", "no-cache");
}
chain.doFilter(request, response);
printResponseHeaders(resp);
}
private void printRequestHeaders(HttpServletRequest request) {
System.out.println("Request headers:");
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
System.out.println(name + " : " + values.nextElement());
}
}
System.out.println();
}
private void printResponseHeaders(HttpServletResponse response) {
System.out.println("Response headers:");
Collection<String> names = response.getHeaderNames();
for (String name : names) {
Collection<String> values = response.getHeaders(name);
for (String value : values) {
System.out.println(name + " : " + value); }
}
System.out.println("**********************************************");
}
}
创建一个web app, 名叫cache。将4张图片复制到webapp/img/目录下。创建上面的filter。
最后在index.html上面展示:
<html>
<body>
<div>
<img src="/cache/img/public.jpg"/>
</div>
<div>
<img src="/cache/img/private.jpg"/>
</div>
<div>
<img src="/cache/img/noStore.jpg"/>
</div>
<div>
<img src="/cache/img/noCache.jpg"/>
</div>
</body>
</html>
打包,并部署到tomcat服务器。
缓存服务器开发
自从Apache 2.2开始,cache_module已经成为非常稳定模块。它完全遵循HTTP协议中的cache规定。所以我们可以使用cache_module来做实现Apache Httpd的缓存管理。
cache_module是由两个实现模块mem_cache_module和disk_cache_module所支持的。其中mem_cache_module是缓存于内存中,disk_cache_module则存于硬盘上。各种详细的命令可以参照Apache手册。本实验,只使用cache_module和disk_cache_module来管理HTTP缓存。只涉及到了部分命令。
修改Apache下的httpd.cnf
LoadModule cache_module modules/mod_cache.so LoadModule disk_cache_module modules/mod_disk_cache.so LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
然后修改Apache下的httpd-vhosts.conf。
NameVirtualHost *:80
<VirtualHost *:80>
ServerAdmin joey
ServerName www.test.com
ErrorLog "logs/cache.log"
CustomLog "logs/accessCache.log" common
# 开启debug
LogLevel debug
ProxyRequests Off
ProxyPreserveHost On
ProxyPass / ajp://localhost:9009/
ProxyPassReverse / ajp://localhost:9009/
# 如果header中没有max-age或者expires,则添加默认过期时间,默认1小时.
CacheDefaultExpire 3600
CacheRoot D:/share/tmp
CacheEnable disk /cache
CacheDirLevels 5
CacheDirLength 3
UseCanonicalName On
</VirtualHost>
在apache2.2中,cache的log还不完善。如果使用2.4版本,可以开启关于cache命中决策的log。2.2中,只能通过在ErrorLog中搜索mod_cache.c关键字,查看cache的处理日志。
检测结果
使用检测工具为Fiddler2, 一个可以捕获所有浏览器对外交流的软件。还可以使用Firebug, chrome debug tool。
在执行下面步骤之前,必须确保明白一件重要的事情,下面所有的步骤都是在浏览器地址栏输入地址后,按enter键产生的。 不可以使用刷新键。
-
(ie)用ie首次访问 http://www.test.com/cache
在代理服务器端查看cache.log,或cache文件夹,可以发现代理服务器对4张图片的处理:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action request to tomcat
status 200request to tomcat
status 200request to tomcat
status 200request to tomcat
status 200Handle cache cached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: privatenot cached
reason: no-storecached
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT浏览器端对4张图片的处理:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status 200 200 200 200 Handle cache cached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTcached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: no-storecached
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT -
(ie,10分钟内)在浏览器缓存和代理缓存均未过期,chrome访问http://www.test.com/cache
代理服务器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action n/a request to tomcat
status 200revalidate to tomcat
status 304Handle cache n/a n/a not cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT浏览器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status use cache directly use cache directly 200 304 Handle cache n/a n/a not cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT -
(chrome,10分钟内)在代理缓存新鲜期内,换一个浏览器,chrome访问http://www.test.com/cache
代理服务器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action cache is fresh.
use cache.request to tomcat
status 200request to tomcat
status 200revalidate to tomcat
status 304Handle cache not cached
reason: privatenot cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT浏览器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status 200 200 200 200 Handle cache cached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTcached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: no-storecached
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT -
(chrome,10分钟后)在10分钟后,代理的缓存和浏览器的缓存都过期了。chrome访问 http://www.test.com/cache
代理服务器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action revalidate to tomcat
status 304revalidate to tomcat
status 304request to tomcat
status 200revalidate to tomcat
status 304Handle cache Replace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: privatenot cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT浏览器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status 304 304 200 304 Handle cache replace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTreplace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: no-storereplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT -
(ie,在第4步后,立刻)这时候,ie的缓存已经过期,但proxy端的缓存是新的。
代理服务器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action cache is fresh,
use cacherevalidate to tomcat
status 304request to tomcat
status 200revalidate to tomcat
status 304Handle cache not cached
reason: privatenot cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT浏览器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status 304 304 200 304 Handle cache replace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTreplace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: no-storereplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMTapache 缓存配置
http://kinglord2010.iteye.com/blog/1442868 -
硬盘缓存:mod_disk_cache,依赖 mod_cache 模块
内存缓存:mod_mem_cache,依赖 mod_cache 模块
文件缓存:mod_file_cache 搭配 mod_mem_cache 模块使用
1、硬盘缓存:配置例子:
- <IfModule mod_disk_cache.c>
- CacheDefaultExpire 86400
- CacheEnable disk /
- CacheRoot /tmp/apacheCache
- CacheDirLevels 5
- CacheDirLength 5
- CacheMaxFileSize 1048576
- CacheMinFileSize 10
- </IfModule>
CacheDefaultExpire: 设定缓存过期的时间(秒),默认是1小时,只有当缓存的文档没有设置过期时间或最后修改时间时这个指令才会生效
CacheEnable:启用缓存,第1个参数是缓存类型,第2个参数是缓存路径,指的是 url 路径,这里是缓存所有的东西,直接写上“/”即可,如“/docs”则只缓存 /docs 下的所有文件
CacheRoot:缓存文件所在的目录,运行 Apache 的用户(如daemon 或 nobody)要能对其进行读写,如果不清楚的话可以直接设置成 777,请手动建立该目录并设置好访问权限
CacheDirLevels:缓存目录的深度,默认是3,这里设置为5
CacheDirLength:缓存目录名的字符长度,默认是4,这里设置为5
CacheMaxFileSize 和 CacheMinFileSize :缓存文件的最大值和最小值(byte),当超过这个范围时将不再缓存,这里设置为 1M 和 10bytes
2、内存缓存:- <IfModule mod_mem_cache.c>
- CacheEnable mem /
- MCacheMaxObjectCount 20000
- MCacheMaxObjectSize 1048576
- MCacheMaxStreamingBuffer 65536
- MCacheMinObjectSize 10
- MCacheRemovalAlgorithm GDSF
- MCacheSize 131072
- </IfModule>
CacheEnable:启用缓存,使用基于内存的方式存储
MCacheMaxObjectCount:在内存中最多能存储缓存对象的个数,默认是1009,这里设置为20000
MCacheMaxObjectSize:单个缓存对象最大为 1M,默认是10000bytes
MCacheMaxStreamingBuffer:在缓冲区最多能够放置多少的将要被缓存对象的尺寸,这里设置为 65536,该值通常小于100000或 MCacheMaxObjectSize 设置的值
MCacheMinObjectSize:单个缓存对象最小为10bytes,默认为1bytes
MCacheRemovalAlgorithm:清除缓存所使用的算法,默认是 GDSF,还有一个是LRU,可以查一下 Apache 的官方文档,上面有些介绍
MCacheSize:缓存数据最多能使用的内存,单位是 kb,默认是100kb,这里设置为128M3、 文件缓存:
#缓存内容
MMapFile /var/www/html/index.html /var/www/html/articles/index.html
#只缓存文件的句柄
CacheFile /var/www/html/index.html /var/www/html/articles/index.html
信息参考:http://httpd.apache.org/docs/2.2/caching.html