Home | 简体中文 | 繁体中文 | 杂文 | 知乎专栏 | Github | OSChina 博客 | 云社区 | 云栖社区 | Facebook | Linkedin | 视频教程 | 打赏(Donations) | About
知乎专栏多维度架构 微信号 netkiller-ebook | QQ群:128659835 请注明“读者”

2.3. 缓存技术

首先要说明,很多缓存技术依赖静态化。下面展示了缓存可能出现的位置。

用户user -> 浏览器缓存 IE/Firefox Cache -> 逆向代理缓存 Reverse proxy Cache -> WEB服务器缓存 Apache cache -> 应用程序缓存 php cache -> 数据库缓存 database cache

当然交换机,网络适配器,硬盘上也有Cache 但这不是我们要讨论的范围。

缓存存储方式主要是内存和文件两种,后者是存于硬盘中。

网站上使用的缓存主要包括五种:

  1. 浏览器 缓存

  2. 逆向代理/CDN缓存

  3. WEB服务器缓存

  4. 应用程序缓存

  5. 数据库缓存

将上面的缓存合理地,有选择性的使用可大大提高网站的访问能力。

总之,想让你的网站更快,更多并发,答案是cache,cache 再 cache

2.3.1. 浏览器缓存

2.3.1.1. Cache-Control

通过 Cache-Control 设置页面缓存时间

max-age
max-age 格式写为:max-age=n,n是以秒为单位, 这个值是告知客户端GMT + N 后页面过期,缓存服务器在s-maxage值为空的时候也会使用这个参数的值。

s-maxage
s-maxage的格式跟max-age一样,只不过他是给缓存服务器使用的。

must-revalidate
这个参数用来告知客户端和缓存服务器,在GET请求的时候必须与源服务器验证实体是否为最新版本。

Cache-Control:max-age=1200,s-maxage=3600
			
Last-Modified
这个参数提供了实体最近一次被修改的时间。这个名字起得不错,当实体被修改了之后,这个参数也就会被修改.
			

ETag

ETag
ETag是根据内容生成的一段hash字符串,采用信息摘要算法,保证每一个页面有一个唯一字串。
			

expires

expires 是HTTP 1.0 中定义的,已经不能满足用户的需要在 HTTP 1.1 加入了max-age,建议使用 max-age替代expires

指令					含义
public				可以在任何地方缓存
private				只能被浏览器缓存
no-cache			不能在任何地方缓存
must-revalidate		缓存必须检查更新版本
proxy-revalidate	代理缓存必须检查更新版本
max-age				内容能够被缓存的时期,以秒表示
s-maxage			覆盖共享缓存的max-age设置
			

在Squid, Varnish, Apache, Lighttpd, Nginx 中都可是实现HTTP Cache-Control推送,每次修改都需要重新加载,不太灵活。

ExpiresActive On
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"
ExpiresByType application/x-shockwave-flash "access plus 1 month"


server.modules = (
...
"mod_expire",
...
)

$HTTP["url"] =~ "^/images/" {
expire.url = ( "" => "access 30 days" )
}
			

我喜欢自己控制TTL时间,且每个页面单独设置,可以随时调整设置。

2.3.1.1.1. 在程序中灵活操作 Cache-Control

在MVC框架中每个控制器下的方法都可以单独操作Cache

Class blog extend Controller{
	blog(){
		header('Cache-Control: max-age=28800');
	}
	list(){
		header('Cache-Control: max-age=3600');
	}
	details(){
		header('Cache-Control: max-age=160');
	}
}
				

你还可以封装到Controller中

Class blog extend Controller{
	blog(){
		this->cache('28800');
	}
	list(){
		this->cache('3600');
	}
	details(){
		this->cache('160');
	}
}
				
2.3.1.1.2. 非程序文件缓存处理

首先做一个Rewrite让程序接管所有图片请求

url.rewrite = ( "^/(.+)" => "/index.php/$1" )
				

然后程序通过PATHINFO取出图片URL

http://images.example.com/your/dir/test.jpg => http://images.example.com/index.php/your/dir/test.jpg
				

程序取出 /your/dir/test.jpg 设置 Content-type 并输出二进制流

详细参考

				
<?php
    // Test image.
    $images = '/test/foo.png';

    $headers = apache_request_headers();

    if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == filemtime($images))) {
        header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($images)).' GMT', true, 304);
    } else {
        header('Content-Type: image/png');
        print file_get_contents($fn);
		if (file_exists($images)) {
			header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($images)).' GMT', true, 200);
			header("Cache-Control: max-age=3600, must-revalidate");
			header('Content-Length: '.filesize($images));
			header('Content-type: ' .mime_content_type($images));
			flush();
			readfile($images);
			exit;
		}
    }
				
					

javascript 文件也可以使用类似方法处理

				
	private function js($file){
		if (file_exists($file)) {
			header("Cache-Control: max-age=3600, must-revalidate");
			header('Content-type: text/javascript');
			flush();
			readfile($file);
			exit;
		}
	}
				
					

2.3.1.2. Expires

只要向浏览器输出过期时间HTTP协议头,不论是html还是动态脚本,都能被缓存。

HTML META

				
<meta http-equive="Expires" content=" Mon, 10 Jan 2000 00:00:00 GMT"/>
<meta http-equive="Cache-Control" content="max-age=300"/>
<meta http-equive="Cache-Control" content="no-cache"/>
				
				

动态脚本

				
Expires: Mon, 10 Jan 2000 00:00:00 GMT
Cache-Control: max-age=300
Cache-Control: no-cache

header("Expires: " .gmdate ("D, d M Y H:i:s", time() + 3600 * 24 * 7). " GMT");
header("Cache-Control: max-age=300");
header("Cache-Control: no-cache");
				
				

很多web server都提供 Expires 模块

[提示]提示

有些浏览器可能不支持。

2.3.1.3. If-Modified-Since / Last-Modified

If-Modified-Since 小于 Last-Modified 返回 200

				
neo@neo-OptiPlex-780:/tmp$ curl -I http://www.163.com/
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=GBK
Transfer-Encoding: chunked
Vary: Accept-Encoding
Expires: Mon, 16 May 2011 08:12:05 GMT
Cache-Control: max-age=80
Vary: User-Agent
Vary: Accept
Age: 38
X-Via: 1.1 ls100:8106 (Cdn Cache Server V2.0), 1.1 lydx156:8106 (Cdn Cache Server V2.0)
Connection: keep-alive
Date: Mon, 16 May 2011 08:11:23 GMT
				
				

If-Modified-Since 大于 Last-Modified 返回 304

				
neo@neo-OptiPlex-780:/tmp$ curl -H "If-Modified-Since: Fri, 12 May 2012 18:53:33 GMT" -I http://www.163.com/
HTTP/1.0 304 Not Modified
Content-Type: text/html; charset=GBK
Cache-Control: max-age=80
Age: 41
X-Via: 1.0 ls119:80 (Cdn Cache Server V2.0), 1.0 lydx154:8106 (Cdn Cache Server V2.0)
Connection: keep-alive
Date: Mon, 16 May 2011 08:11:14 GMT
Expires: Mon, 16 May 2011 08:11:14 GMT
				
				

2.3.1.4. ETag / If-None-Match

				
neo@neo-OptiPlex-780:/tmp$ curl -I http://images.example.com/test/test.html
HTTP/1.1 200 OK
Cache-Control: s-maxage=7200, max-age=900
Expires: Mon, 16 May 2011 09:48:45 GMT
Content-Type: text/html
Accept-Ranges: bytes
ETag: "1984705864"
Last-Modified: Mon, 16 May 2011 09:01:07 GMT
Content-Length: 22
Date: Mon, 16 May 2011 09:33:45 GMT
Server: lighttpd/1.4.26
				
				
				
neo@neo-OptiPlex-780:/tmp$ curl -H 'If-None-Match: "1984705864"' -I http://images.example.com/test/test.html
HTTP/1.1 304 Not Modified
Cache-Control: s-maxage=7200, max-age=900
Expires: Mon, 16 May 2011 09:48:32 GMT
Content-Type: text/html
Accept-Ranges: bytes
ETag: "1984705864"
Last-Modified: Mon, 16 May 2011 09:01:07 GMT
Date: Mon, 16 May 2011 09:33:32 GMT
Server: lighttpd/1.4.26
				
				

2.3.2. CDN (Content Delivery Network) 与反向代理缓存

具有代表性的逆向代理服务器:

  1. Squid

  2. Nginx

  3. Varnish

  4. Apache cache module

其它逆向代理服务器

  1. 一些提供cache的硬件设备

  2. 最近几年出现了的 China Cache 服务商,也称CDN

很多CDN厂商使用Squid 二次开发做为CDN节点,通过全球负载均衡使用分发

这些CDN厂商主要做了一下二次开发

  1. logs 日志集中

  2. 流量限制

  3. push,pull操作

  4. url 刷新

s-maxage 与 max-age用法类似,s-maxage针对代理服务器缓存。同样适用于CDN

s-maxage 与 max-age 组合使用可以提高CDN性能

2.3.2.1. CDN接口API

与CDN有关的开发工作

CDN 内容更新,一般厂商会提供一个SOAP接口,你可以通过接口刷新你的内容。但接口有限制,不能随意使用,一般是多少秒可以刷新一次,或者一天可以刷新几次

2.3.2.2. 方向代理页面过期处理

方向代理一般都支持PURGE协议,Squid,Varnish等等向管理端口发送 PURGE 即可是使用页面刷新

PURGE http://netkiller.github.net/index.html
				

有些方向代理如:Varnish 可以使用正则表达式

同时这些代理服务器都承受管理命令

squid: squidclient

varnish: varnishadm

2.3.2.3. 内容版本化

例如这样的URL

http://images.example.com/logo.gif
http://images.example.com/product.jpg
				

我们可以通过Rewrite或PATHINFO等技术做为静态化。例如首次版本

http://images.example.com/logo.1.gif		=> logo.gif
http://images.example.com/product.1.jpg		=> product.jpg
				

原图发生变化后,版本递增

http://images.example.com/logo.2.gif		=> logo.gif
http://images.example.com/product.2.jpg		=> product.jpg
				

就的URL将丢弃

http://images.example.com/logo.1.gif
http://images.example.com/product.1.jpg
				

CDN 就回源去下面的URL,并且取到的是新图

http://images.example.com/logo.2.gif
http://images.example.com/product.2.jpg
				

2.3.3. 负载均衡设备

F5 Big-IP, Array 等设备都提供硬件加速,其原理与squid, apache提供的功能大同小异

其中Array 页面压缩采用硬件压缩卡实现,SSL加速也采用硬件实现

2.3.4. WEB服务器缓存

例如,通过配置apache实现自身 cache

2.3.5. 应用程序缓存

在这个领域百花齐放,相信你一定能找到适合你的。这些cache会为你提供一些api,来访问它。

代表性的 memcached 据我所是sina广泛使用,腾讯也曾经使用过后来开发了TC(Tencent Cache),台湾雅虎则使用APC Cache。

另外模板引擎也有自己的缓存系统

2.3.6. 数据库缓存

数据库本身就有这个配置选项,如果需要你仍然可以在数据库前面加一道Cache。

例如PostgreSQL, MySQL 都提供参数可以将memcached编译到它内部