Loading...

    AD: 猛买 | 快递查询 | Jobsdigg | 很棒的男装店

8行代码实现SuperCache

可任意转载,但必须在醒目位置以超链接形式标明文章原始出处和作者信息
原文地址:http://www.blogkid.net/archives/2179.html

使用wordpress的人应该听过wp-supercache的大名,这是一款wordpress插件,用以为blog上的内容生成静态缓存。这样,即使有再多的访问请求,服务器也只需要处理静态文件。我不知道王小峰有没有用wp-supercache,只看到他在抱怨wp太慢(确实挺慢)。

我在使用wp-supercache过程中发现它非常棒,让人甚至感觉不到缓存的存在。我登录以后,在文章页面上会显示出编辑文章的链接,退出登录打开就是一个普通浏览者。我好奇,它是如何判断访问者身份的呢?昨天终于有机会一探究竟,然后在自己的项目中也实现了一个类似的super_cache。

其实奥妙在于wp-supercache在.htaccess中写入的一条段涉及到rewrite的内容:

RewriteCond %{REQUEST_METHOD} !=POST
RewriteCond %{QUERY_STRING} !.*s=.*
RewriteCond %{QUERY_STRING} !.*p=.*
RewriteCond %{QUERY_STRING} !.*attachment_id=.*
RewriteCond %{QUERY_STRING} !.*wp-subscription-manager=.*
RewriteCond %{HTTP_COOKIE} !^.*(comment_author_|wordpress|wp-postpass_).*$
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{DOCUMENT_ROOT}/wp-content/cache/supercache/%{HTTP_HOST}/$1/index.html.gz -f
RewriteRule ^(.*) /wp-content/cache/supercache/%{HTTP_HOST}/$1/index.html.gz [L]

标红的这一行的作用是,如果cookie中包含comment_author/wordpress/wp-postpass_这样的字符,则不进行rewrite。也就是说,当我登录以后,打开任何一个页面都是没有被缓存过的页面;而如果我退出,相应cookie将被清空,此时如果存放缓存的目录有生成好的缓存文件,则进行rewrite,服务器将静态文件发送给我。

我恍然大悟。大部分人其实是“游客”,所以不会带有导致缓存不工作的cookie,这种缓存方式很适合wordpress这样的cms。于是我考虑把这种缓存方式迁移到我的rails项目中。

rails自带了page cache,  action cache 以及fragment cache(还可进行对象缓存,rails还有sql cache,暂不讨论)。其中page cache就是生成静态文件在服务器上,速度最快;action cache速度其次,因为毕竟把action输出的内容缓存了,消耗的是进入rails、执行前置后置过滤器的时间;fragment cache就是页面中的片段缓存,速度最慢。

但rails的page cache默认会生成文件到/public目录。比如这个地址:http://www.niupu.com/subject/311898/,默认生成的page cache是 /public/subject/311898.html。如果下次再有人访问,会直接把静态文件返回。在这种情况下,虽然也可以通过校验cookie使该请求不用静态文件去响应,但这样有点“逆天而行”的感觉,而且很难操作。如果能把缓存文件放到个别的地方(就像wp-supercache),那就好了。

好在rails提供了足够的灵活性。首先需要更换一个保存缓存文件的目录,在environment.rb的 Rails::Initializer.run 这一block中加入一句:

config.action_controller.page_cache_directory = RAILS_ROOT+”/public/cache/”

这就把存放缓存文件的位置,由/public目录改到了/public/cache目录。

然后在相应Controller中加入page cache的代码:

caches_page   :index

现在到生产环境下(或者开发环境下打开perform_cache)访问相应页面,还是以刚才的地址为例,可以看到已经在/public/cache/subject/下生成了名为311898.html的缓存文件。

等一下,如果是一个登录后的用户访问此页面,按照现在的逻辑,也会将响应的内容缓存下来,其中或许有一些私人内容不该被缓存的。我在此处引入了一个cookie来解决问题,该cookie也将在后面被.htaccess文件使用。

caches_page :index , :if => Proc.new{|c| c.request.cookie[:user_id].blank?}

此处意思是只有当cookie中的user_id为空时,才进行page cache。这个user_id,可以在用户登录时顺便设置一个进去,不再赘述。

下一步去设置Rewrite规则,我写个我的版本,大家可以参照:

RewriteCond %{QUERY_STRING} !.*=.*
RewriteCond %{HTTP_COOKIE} !^.*(user_id=\d+).*$
RewriteCond %{DOCUMENT_ROOT}/cache/$1.html -f
RewriteRule ^/(.*)/$ /cache/$1.html [L]

至此,一个rails版的supercache已经成功实现。增加代码4行(包括登录与退出时操作cookie),增加rewrite规则4行,共8行代码,就能在rails项目上实现类似wp-supercache的功能。只能说,酷。

PS: 对于链接里带有UTF8中文的情况,现在的缓存在windows服务器(因为编码的问题)还不能正常工作,linux上应该是可以的。

参考:

1、Apache module mod_rewrite

2、Documentation of rails about page caching

1 Response to “8行代码实现SuperCache”


  1. [...] 买了VPS以后,不甘只用apache。于是我换上了nginx做Web Server。但原来rewrite规则需要“翻译”成nginx的语法,这里就出了问题。我的blog使用着supercache,另一个网站使用了类似的静态缓存(请看我另一篇文章:8行代码实现supercache)。问题出在哪里呢?supercache的实现方式,是基于多组合条件的rewrite,但nginx恰恰不支持嵌套的或并列的 if 判断语句,于是没法直接支持多条件的rewrite。 [...]

Leave a Reply