页面静态化是前端优化的一个重要方法,一般采用生成静态文件的方式实现。这里我尝试采用另外一种方式去实现,就是直接把页面用Memcached进行缓存,然后通过Nginx直接去访问。
采用Memcached缓存页面的好处是什么呢?
Nginx内置了Memcached模块ngx_http_memcached_module,可以很轻松的实现对Memcached的访问。我这里做一个示例,通过PHP缓存我们邮轮网站的首页,然后通过URLhttp://dev.hwtrip.com/cache/index.html去访问这个页面。
首先,我们对Nginx进行配置:
server {
listen 80;
server_name dev.hwtrip.com;
location ^~ /cache/ {
set $memcached_key $request_uri;
memcached_pass 127.0.0.1:11211;
}
error_page 404 502 504 = @fallback;
}
location @fallback {
proxy_pass http://backend;
}
这个配置把所有请求URI前缀为/cache/的访问用Memcached模块进行内容的读取,同时使用请求URI作为Memcached的key。当缓存没有命中或者出错时,我们使用@fallback进行处理(例如访问实际的应用并重新写入缓存),这个不在这里展开了。
然后,我们用简单的代码把页面写进Memcached里:
$htmlContent = file_get_contents('http://youlun.hwtrip.com');
$memcached = new Memcache();
$memcached->addServer('127.0.0.1', 11211);
$memcached->set('/cache/index.html', $htmlContent);
注意写缓存时的key,由于我们访问的URL是http://dev.hwtrip.com/cache/index.html,所以写进Memcached的key就是URI/cache/index.html。
执行完代码后,我们访问下http://dev.hwtrip.com/cache/index.html:
可以看到,通过nginx很容易实现对Memcached进行访问,但是这离我们缓存页面的目标还差很多,因为有两个大问题还没有解决。
Nginx可以通过upstream支持访问多个Memcached服务节点:
upstream memcached {
server 127.0.0.1:11211;
server 127.0.0.1:11212;
server 127.0.0.1:11213;
server 127.0.0.1:11214;
}
server {
listen 80;
server_name dev.hwtrip.com;
location ^~ /cache/ {
set $memcached_key $request_uri;
memcached_pass memcached;
}
error_page 404 502 504 = @fallback;
}
......
但是,upstream采用的是round-robin的轮询方式,而我们知道PHP的php_memcache扩展使用的是一致性哈希算法进行Memcached服务节点的选择。于是乎,我们在前端用PHP缓存的页面,通过nginx不一定能访问到。所以我们必须让Nginx也能通过一致性哈希算法去选择节点。
这里我们用到了ngx_http_upstream_consistent_hash这个第三方模块,这个模块实现了跟php_memcache这个PHP扩展一样的一致性哈希算法。
重新编译Nginx,添加好这个模块,我们修改下Nginx的配置文件的upstream部分:
upstream memcached {
consistent_hash $request_uri;
server 127.0.0.1:11211;
server 127.0.0.1:11212;
server 127.0.0.1:11213;
server 127.0.0.1:11214;
}
......
我们修改下缓存页面的示例PHP代码:
$htmlContent = file_get_contents('http://youlun.hwtrip.com');
$memcached = new Memcache();
$memcached->addServer('127.0.0.1', 11211);
$memcached->addServer('127.0.0.1', 11212);
$memcached->addServer('127.0.0.1', 11213);
$memcached->addServer('127.0.0.1', 11214);
for ($i = 1; $i < 10; $i ++) {
$cacheIns->set("/cache/index$i.html", $htmlContent);
}
通过设置不同的key,我们测试下Nginx是否能获取到正确的内容。经测试,PHP设置的缓存,用Nginx都能正常访问到。
这是第一个例子里Nginx返回的页面的响应头:
可以看到没有返回任何缓存相关的响应头,这样每次访问,浏览器都会去请求服务器,虽然服务器有缓存,但明显不符合我们对性能优化的追求。不过就算Nginx返回了相关的响应头,然后我们请求的时候包含了If-Modified-Since这个请求头,Ngxin的memcached模块也不会去判断这个请求有没有过期以及返回304 Not Modified。所以我们需要实现两件事,第一是能让Ngxin返回正确的响应头,第二是能让Nginx判断请求的资源是否过期,并正确返回响应码。
这里,我们借助另外一个Nginx的第三方模块:gx_http_enhanced_memcached_module。这个模块提供了很多功能,大家可以到它的github页面上了解。这里我们主要用到它的两个功能:
upstream memcached {
consistent_hash $request_uri;
server 127.0.0.1:11211;
server 127.0.0.1:11212;
server 127.0.0.1:11213;
server 127.0.0.1:11214;
}
server {
listen 80;
server_name dev.hwtrip.com;
location ^~ /cache/ {
set $enhanced_memcached_key $request_uri;
enhanced_memcached_pass memcached;
}
error_page 404 502 504 = @fallback;
}
......
我们再次修改示例PHP文件:
$htmlContent = file_get_contents('http://youlun.hwtrip.com');
// 页面过期时间
$expiresTime = 60 * 5;
// Last-Modified头设置的时间
$lastModified = gmdate('D, d M Y H:i:s \G\M\T', time());
// Expires头设置的时间
$expires = gmdate('D, d M Y H:i:s \G\M\T', time() + $expiresTime);
// 最终缓存的内容
$cacheContent = "EXTRACT_HEADERS
Content-Type: text/html
Cache-Control:max-age=$expiresTime
Expires:$expires
Last-Modified:$lastModified
$htmlContent";
// 获取memcache实例
$memcached = new Memcache();
$memcached->addServer('127.0.0.1', 11211);
$memcached->addServer('127.0.0.1', 11212);
$memcached->addServer('127.0.0.1', 11213);
$memcached->addServer('127.0.0.1', 11214);
// 写入缓存
$memcached->set('/cache/index.html', $cacheContent, $expiresTime);
这次我们设置了缓存的过期时间,并在缓存内容前面添加了一些响应头。ngx_http_enhanced_memcached_module模块EXTRACT_HEADERS这个标记去识别并记录响应头,详情请看github页面的说明。
重新写入缓存后,我们再次访问页面:
可以看到缓存相关的响应头都已正确返回。
至此,我们已经简单的完成了使用Nginx和Memcached对缓存页面的访问,但这只是后端的简单实现,在前端还需要实现对页面缓存的管理等等的工作。