web server cache - yaokun123/php-wiki GitHub Wiki

Web服务器缓存

一、URL映射

对于任何的web服务器,当我们向他发送一个http请求后,它要做的主要工作就是解析URL,然后完成从URL到实际内容或资源的映射。这里所说的映射是一个抽象概念,实际上就是服务器处理请求并生成响应内容的过程,而这里之所以说“映射”,是希望从URL的角度来看服务器的处理过程。

URL映射的过程获取你并不陌生,比如我们请求以下URL:

http://highperfweb.com/book/index.html

这时候wen服务器也许会将它定位到以下文件路径

/data/www/book/index.html

你一定看出来了,web服务器为这个站点设置的根目录是/data/www,没错,看起来很简单,web服务器只需要读取这个文件的内容即可,这就是最终要传送给用户的内容了,这部分的开销主要在磁盘I/O操作。

等等,一定是这样吗?假如我们使用了URL Rewrite(地址重写)技术,比如apache的mod_rewrite,那么以上URL完全有可能重写到另一个文件路径中,比如我们Rewrite规则使URL发生了如下重写

/book/index.html -> /pages/book/index.html

经过内部重写,现在Web服务器需要访问的文件变成了:

/data/www/pages/book/index.html

这还不算什么,我决定将它重写到一个动态内容中,规则产生的结果如下:

/book/index.html -> /reader.php?book=index

这种重写很常见,他让URL变得跟更加优雅,富有观赏性,同时有利于搜索引擎的收录,与此同时,这使得Web服务器从原本读取静态文件,变成了生成动态内容,增加了不少开销。

URL映射也许还没有结束,假如Web服务器作为反向代理(Reverse Proxy)将请求转发给了后端其他服务器,还会发生以下重写:

/reader.php?book=index -> http://backend/reader.php?book=index

这样以来又增加了一定的网络I/O开销。

在实际情况中,URL映射的过程可能还需要经历更多的环节,比如SSI模式下对于内容的通读。如果我们不看过程,只关注URL和最终的响应内容,则它们的确就像一系列的对应关系。

二、缓存响应内容

你也许想到了,我们为什么不将这些对应关系缓存起来呢?很多时候,一个URL在一段较长的时间内对应一个唯一的响应内容,比如静态内容或者更新不太频繁的动态内容,一旦将最终的内容缓存后,下次Web服务器便可以在收到请求后立即拿出事先缓存好的响应内容并返回给浏览器,因为这个操作发生在所有其他行为之前,所以必定会节省一定时间。

幸运的是,将这部分任务交给Web服务器来做是最合适不过的了,而且大多数情况下我们不需要亲自去实现它,主流的Web服务器软件都会提供类似的支持,比如apache的mod_cache,它在URL映射开始时检查缓存,如果缓存存在并处于有效期内,那么将直接取出作为响应内容返回给浏览器。

准备好缓存区

当然还有一点很重要,那就是将缓存内容存储在什么位置,一般来说本机内存和磁盘是主要选择,当然,也可以采用分布式设计,存储到其他服务器的内存或磁盘中。

同样apache也提供了两个扩展,分别是mod_disk_cache和mod_mem_cache,他们为mod_cache提供存储引擎,前者使用磁盘文件系统来存储缓存,后者使用内存。但实际上,这两个存储引擎一直都处于实验阶段,到目前为止,我看到apache官方已经将mod_mem_cache从apache最新文档的模块列表里清除掉了,根据Apache社区的一些讨论,可能是mod_mem_cache的实现机制导致它在Apache多进程模型下共享内存缓存的开销较大,官方推荐使用mod_disk_cache来取代mod_mem_cache,因为他在磁盘上维护了一块多个进程共享的缓存区,并且由于磁盘文件系统缓冲区以及MMAP的作用,磁盘缓存的访问速度甚至要超过mod_mem_cache实现的内存缓存。

另外,Lighttpd、Nginx等也都有类似机制的缓存支持。在apache中开启磁盘缓存很容易,但是你必须在apache编译的时候,为configure追加必要的模块,如下所示:

--enable-cache=shared --enable-disk-cache=shared --enable-so

然后为apache增加如下配置:

LodeModule cache_module modules/mod_cache.so
LodeModule disk_cache_module modules/mod_disk_cache.so
CacheRoot /data/apache_cache
CacheEnable disk /
CacheDirLevels 5
CacheDirLength 3

在以上的配置中,CacheRoot指定了缓存内容的存储目录,CacheEnable指定了缓存引擎,即磁盘,同时指定将站点根目录下的所有请求都进行缓存,如果你只需要缓存某个目录,那么可以进行如下配置

CacheEnable disk /images

后面的CacheDirLevels和CacheDirLength两个参数,指定了缓存的目录分级结构。

控制有效期

说到缓存过期的问题,也许你得花一点时间回顾以下前面介绍的浏览器缓存时提到的缓存协商和过期时间等HTTP头信息中的标记,没错,这些标记再次派上了用场,Web服务器缓存对于动态内容或静态内容的过期检查机制仍然是建立在HTTP/1.1协议上的对话,除此之外,他们之间没有其他的沟通方式,虽然他们同在一台物理机器上,但保持距离是非常必要的。

要为一个动态内容指定缓存有效期,仍然是在HTTP响应头中追加Expire标记,如果你希望动态内容立即过期,也就是说根本不要缓存这个动态内容,那么最简单的方法就是让Expire为0,如下所示:

header("Expire:0")

这样以来,Web服务器便不会把这个动态内容放入缓存区

可是如果动态内容没有输出Expire标记的话,Web服务器缓存区将如何判断它的有效期呢?这就得用到另外一个我们熟悉的标记了,那就是Last-Modified,如果动态内容的http头信息中包含Last-Modified时间,那么服务器缓存区会使用默认最大缓存时间,比如

CacheMaxExpire 3600

这样Web服务器便可以在日后将过期时间和Last_Modified时间进行对比,以判断内容是否过期。如果动态内容的HTTP头信息中没有Last-Modified时间的话,处理方法取决于mod_cache的另一个配置,即CacheIgnoreNoLastMod,如果开启它的话,动态内容将不会被缓存,因为它既没有Expire也没有Last-Modified,缓存区不愿意接管它,但如果使用CacheIgnoreNoLastMod默认设置,即关闭,缓存区会记录当前时间作为Last-Modified,并且使用默认最大缓存。

另外,我们还希望有些特定的内容可以跳过Web服务器缓存区,虽然我们可以采用将Expire设置为0的方法,但是还有其他的解决方案,比如mod_cache还提供了以下的配置:

CacheIgnoreHeaders set-Cookie

这样以来,凡事需要创建浏览器cookie的动态内容,都可以跳过缓存区,比如用户登陆成功之后生成包含登陆状态的cookies,我们当然不希望它被缓存。

静态内容同样需要控制他在Web服务器缓存区中的有效期,但我想你已经很清楚了,它的本质仍然是那些HTTP头信息中的标记,不同的是,对于静态内容来说,这些都可以通过Web服务器来轻松配置。