Nginx技巧:如何使用X-Accel-Redirect同时输出ETag Header和其他自定义Header
在Nginx中,常见的技巧是后端fastcgi使用X-Accel-Redirect Header来通知nginx使用sendfile加速
文件的输出,避免后端重复读取文件.
当使用X-Accel-Redirect时, 后端输出的一些自定义header就被忽略掉了. 在upstream redirect到
内部location uri后, 这些header并没有被同时输出.
这2天,为了快速解决PHP的性能问题, 就用Perl做的一个Plack application, 用于dispatch MongoDB中的gridfs. 为了加速, 对于gridfs的文件
进行了cache, 并根据文件的md5值输出etag. 这样,当client 请求时, 如果mongodb中的主文件的md5
并没有改变,那么就直接输出304. 否则使用X-Accel-Redirect返回cache file的uri.
使用ETag的原因就是cache文件需要频繁清理, 仅仅依靠last modified time并不可靠.
, Plack的应用也是可以负载到不同的fastcgi upstream.
ETag的验证机制则可以很好的解决这些问题.
因为有很多文件其实很少读取的, 即便某些用户偶然访问, 那么通过ETag如果验证文件在MongoDB中
并没有变动,则直接返回304即可, 无需再重新创建cache文件,从而可以保持cache的热度.
如何能够输出ETag呢, 解决方式就是在X-Accel-Redirect 定向到的location中,使用add_header来
设置附加的ETag header:
location /__file_result__ {
internal;
root /cache;
add_header ETag $upstream_http_etag;
}
这样, 对应的X-Accel-Redirect的文件就有了对应的ETag header.
实际上, 你可以使用add_header加入其他更多的HEADER. 对应的$upstream_http_(header_name).
变量的格式可以在手册的upstream模块部分查到.
BTW, Plack 的效率和性能还是很爽的. 开发效率很高, 10几分钟就能完成一个高效的fastcgi应用.
相对以前手工写, 进步了一大块. 尤其是当你只是想解决几个小问题的时候,相比之下,Cgi::Application都像牛刀了.
闲谈MongoDb+GridFS+Nginx
MongoDb果然是个好东西. 我在最近的一个项目实践中, 实验性的用到了这个东西.
在测试中,对于GridFS相当满意. 首先, 和传统的MogileFS不同, gridfs可以和其它的meta数据部署在同一个
db中,默认的会为gridfs的collection分别创建fs.files和fs.chunks.
当存储一个文件时,可以附加存入任意的附加信息,因为这些信息实际上也是一个普通的collection.
这个特性给我们省了好多的事情. 以前,如果要存储一个附件,通常的做法是,在主数据库中存放文件的属性,并且记录
文件的path.当查询某个文件时,需要首先查询数据库,获得path,然后从存储系统中获得相应的文件.
在使用gridfs时,则非常简单, 我们可以直接将这些信息直接存储到文件中. 比如下面的PHP代码,存储上传的文件到gridfs:
public function store($file,$attrs=array()) {
if (!is_file($file)) {
throw new CZone_Core_Service_Exception("File:$file not exists");
}
$defaults = array(
'content_type'=> null,
'art_id'=>-1,
'state' => self::STORE_STATE_TMP,
'created_on' => time(),
'is_thumb'=> false,
'md5'=> md5_file($file)
);
$asset_attrs = $attrs+$defaults;
if (!isset($asset_attrs['content_type'])) {
$asset_attrs['content_type'] = Doggy_Util_File::mime_content_type($file);
}
$fs = $this->db->get_fs();
return $fs->storeFile($file,$asset_attrs);
}
调用store时,可以附件任意属性数组. 之后, 检索文件时则可以根据这些属性来查找:
public function fetch_by_id($id) {
return $this->fetch(array('_id'=>$id));
}
public function delete_by_id($id) {
return $this->delete(array('_id'=>$id));
}
public function delete_art_assets($art_id) {
return $this->delete(array('art_id'=>$art_id));
}
public function delete_asset($asset_id) {
return $this->delete_by_id($asset_id) && $this->delete_asset_thumbs($asset_id);
}
public function delete($options) {
if (isset($options['_id'])) {
$options['_id'] = Doggy_Mongo_Db::id($options['_id']);
}
$fs = $this->db->get_fs();
return $fs->remove($options);
}
public function find_all($query=array(),$fields=array()) {
return $this->db->fs_find($query,$fields);
}
使用gridfs,可以把原先复杂的操作变得相当简单, 真正实现了mogodb设计者的想法,
数据库为什么不能做文件系统?
在实践中,我发现GridFS和之前研究的MogileFS一些基本方式其实是相通的. 只不过, mogilefs的存储节点是
使用了简化版本的DAV 而已.
从这个角度,完全可以设计一个基于mongo gridfs的mogilefs.
至于性能, 从我的体会来说, 还不错. 毕竟我的项目的目前看存储仅限于TB级别.
不过在生产环境中,国外有用于存储视频流的.
GridFS的一个优点是可以存储上百万的文件而无需担心扩容性.
通过同步复制,可以解决分布式文件的备份问题.
目前,mongo支持主-从和Replica Pairs以及受限的Master-Master Replication.
比较实用的还是前2种.
通过ARP-ping可以实现一个双机热备切换,类似我正在用的mysql的mmm.
在实验过后,感觉使用mongo是非常轻松. 很轻松就解决了高并发中经常会遇到的问题,
比如实时的日志处理,实时的统计,更新某个字段.
通过使用mongo的capped collection,可以实现cache, message queue等特性,无需附加成本.
还有share session.
部署
gridfs的部署的选择方案不多,大概有以下几种:
1. 通过mongo client 的script, 比如PHP.
优点是简单,缺点是每次都要读取mongo数据库. 虽然mongo的性能不错,但是似乎总是不忍.
另外,像PHP的DRIVER并不支持HTTP RANGE header,这样就无法支持断点续传.
2.使用Nginx module
http://github.com/mdirolf/nginx-gridfs
这是gridfs的nginx module. 可以通过nginx直接访问读取mongo gridfs中的文件.
和nginx对应的mogilefs module类似.
优点: 由于直接通过nginx,速度是最快的.
缺点: 只能通过file_path来查找,目前不支持_id来查找.因此必须在file_path上建立索引.
优化方案:
我自己构想了以下的优化方案:
1. squid/varnish+script-backend
在nginx前端加上一个squid或者varnish作为反向加速. 如果没有则通过 PHP脚本来获取.
应用场景: 特别适合读取频繁的文件,比如用户的头像,热门图片,缩略图等. 不适合大文件.
缺点: 文件的过期必须正确设置. 此外配置好varnish或者squid
2. 基于proxy_store或fastcgi-cache, try_files
这种方案的应用场景同1, 但都是使用nginx的相应模块即可实现.
通过对fastcgi/proxy进行cache或store,就可以实现文件按需存储.
当使用proxy_store时,当后端文件变动时,需要purge这些文件.实现起来不难.
对于大文件,我觉得性价比比较高的一个方案是:
使用Perl或者PHP写一个脚本作为fastcgi运行. 前端用nginx进行负载均衡.
如果使用Pelr则当前driver支持随机读取,支持断点续传. 用PHP则需要做个简单处理,
手动判断HEADER,并计算出offset,然后再读取相应的字节流.
注意,如果是用PHP,则最好的方案是单独编译一个PHP,仅保留”最基本”的特性.
这样,可以节省很多的资源占用,稳定性和速度也比较好.我建议的保留的特性有:
json+mongo+spl.
PHP driver要比Perl更为成熟,虽然二者核心开发者都是一个人. Perl目前还是beta,也没有特别
广泛的使用,但据说由于大部分使用的是PHP的C代码,所以还是非常可靠的.
其他一些信息:
1.通过runcommand可以直接在mongodb端运行处理脚本. 比如像mapreduce,或者一些需要读取数据然后进行处理的.
这些command则是使用javascript方式来编写的,很容易. 好处就是避免了数据在服务端和客户端之间的读取和传输,
提高效率.
2. sharding
sharding在目前开发版中已经具备,但还不成熟. 但是可以自己实现sharding比较好.因为目前的sharding还是比较硬性的.
3.灵活使用magic操作符和upsert,比如$inc,$all,$in 等等
这些轻松解决一些麻烦的操作.
3.其他的复制方案
对于文件系统, 其实可以通过一个脚本来定期将文件复制到其他的节点. 实现类似mogilefs的功能.
我对此比较有兴趣.
Nginx+mod_rails(Phusion Passenge)+redmine+gitosis的整合笔记
个人使用Git很长时间了,感觉比subversion方便太多。因此,最近准备将团队的版本管理工具从subversion转向git.
周末花了点时间搭建了一个项目开发构建的环境:
版本管理使用git并用gitosis来搭建。
项目管理使用redmine。
部署:Nginx+Passenge(mod_rails)
中间出现了不少问题,做个笔记,以备后查:
一、安装Git/Gitosis环境
1.安装git
这部分没有什么问题
2.安装gitosis
git环境默认是可以直接使用的,但需要为每个成员开ssh权限帐号,且各个仓库之间的交叉权限管理很麻烦,
所以需要使用gitosis, 它无需为每个开发人员开设ssh帐号,安全方便。
我使用的机器还是Redhat EL4U3, 其中python的版本是2.3, gitosis并不兼容。
所以首先是安装一个独立的python26环境。
之后就简单了:
* 添加一个git用户,用它来运行gitosis,将其home设置为要存放git repositories的目录,注意将owner设置为git.
* clone gitosis
git clone git://eagain.net/gitosis.git
python setup.py install
* 上传你的ssh public key到服务器
scp ~/.ssh/id_rsa.pub root@xxx:/tmp/
* 用你的key初始化gitosis
sudo -H -u git gitosis-init < /tmp/id_rsa.pub
这将创建gitosis-admin 库,位于:
gitosis将使用这个库进行用户和库权限的管理,由于其自身就是一个git repository,所以管理起来很方便:
修改post-update hook:
chmod a+x
现在服务端部署完毕.
git clone git@xxx:gitosis-admin.git
这将checkout/clone 到你本地.
现在将你的开发组成员的public key放到keys目录下,
格式为: user_name.pub , 如night.pub, little_zhang.pub.
然后,修改gitosis.conf:
[gitosis]
[group gitosis-admin]
writable = gitosis-admin
members = night
[group night-team]
members = night
writable = nginx-cache nginx-mirror
[group doggy-team]
members = night
writable = doggy doggy-passport
每个group是一个组,members是成员名,空格分隔, writeable定义该组成员可以写入的repository.
members对应的就是keys目录下的文件名(无pub后缀)。
修改后,提交并push到服务端:
git commit -m’setup users and access’
git push
如何使用?
现在,某个用户就可以创建一个repository并push到服务端了:
假设night这个用户 要创建一个nginx-mirror仓库并push上去, 他需要这样做:
mkdir nginx-mirror
git init
touch .gitignore
git commit -m’init commit’
git remote add origin git@xxx:nginx-mirror.git
git push –mirror
现在本地的nginx-mirror就和服务端的nginx-mirror.git同步了,之后就是正常的git日常管理流程:
add,commit,branch,push….
Git环境至此就告一段落。
二、安装部署Redmine
Redmine是一个Rails项目,和Trac类似,但操作界面要比trac要简单方便很多. 包括项目管理中常用的模块:Issue, Timeline,
Wiki,Files,Documents, 并且可以和Subversion,Git整合, 多数plugin都有中文的locale.
1. 安装Ruby
最新的Ruby是1.9,但Rails2.2不支持,只能安装1.8
2.安装Rails 2.2
3. 安装mysql和其他可选模块,如RMagic
4.Checkout最新的代码
svn co svn://rubyforge.org/var/svn/redmine/trunk redmine
5. 配置数据库
创建project数据库,并修改config/database.yml
6.Load数据库并插入初始数据
rake config/initializers/session_store.rb
rake db:migrate RAILS_ENV=”production”
rake redmine:load_default_data RAILS_ENV=”production”
7. 创建并修改临时目录的权限
mkdir tmp public/plugin_assets
chmod -R 777 files log tmp public/plugin_assets
8.测试
ruby script/server webrick -e production
现在,3000端口就可以进入,默认的用户 admin/admin
9. 部署Redmine
Redmine是一个rails应用, 所以如何部署是和普通rails一样, 我使用nginx+mod_rails方式,简单,高效。
安装 mod_rails(http://www.modrails.com/)
gem install passenger
现在需要安装passenger的nginx模块, 其自带了一个passenger-install-nginx-module命令,
可以自动编译并安装nginx,不过我嫌啰嗦,自己编译:
cd /usr/local/src/nginx-0.7.58
./configure –with-http_stub_status_module –with-http_gzip_static_module –add-module=’/usr/local/lib/ruby/gems/1.8/gems/passenger-2.2.2/ext/nginx’
make install
现在修改nginx.conf:
在http段加入:
passenger_root /usr/local/lib/ruby/gems/1.8/gems/passenger-2.2.2;
然后在虚拟主机启用passenger:
server {
listen 80;
server_name dev.xxx.com;
access_log /logs/dev.access.log;
index index.html;
passenger_enabled on;
root /project/redmine/public;
}
现在重启nginx,访问上述域名即可.Passenge会自动监测并重启ruby进程.
其他高级配置可以参考相应文档.
三、整合Gitosis和redmine
这部分稍微有点小麻烦。
1. 创建新项目,在版本库中SCM选择git,
然后将gitosis的仓库位置填入.
2. 刷新,并访问版本库模块,此时出错,显示没有相应的修订条目
这个问题主要是由于gitosis创建的仓库的权限是750,而gitosis和redmine的运行
用户不同.因此,需要将redmine的运行用户调成git,这样才能读取仓库的信息。
感谢mod_rails(Passenge), 它支持user switching, 由于redmine是rails程序,
因此只需要改变config/environment.rb文件owner即可,mod_rails默认将该应用切换这个文件的owner:
chown /project/redmine/config/environment.rb
killall -HUP nginx
现在,可以在redmine中浏览版本库了.
3. Hack redmine支持branches/tags的浏览
还有一个小问题,那就是默认的redmine的git插件只能浏览HEAD/master的信息,而无法查看branche/tags的信息,
这是我不能容忍的。
所以,需要对redmine打个补丁.
cd /project/redmine
path -p1 < ../redmine_git_branches.diff
killall -HUP nginx
现在重新刷新,wow!, 所有的分支和tag都能够浏览了,虽然还没有像github那样完美,但至少满足我的工作要求了。
这是我所使用的patch:redmine_git_branches,可以对现在trunk版本直接patch。
=====================END===================
nginx 0.6.32 更新
更新nginx到0.6.32. 这是稳定版,变化不大:
Changes with nginx 0.6.32 07 Jul 2008
*) Change: the "none" parameter in the "ssl_session_cache" directive;
now this is default parameter.
Thanks to Rob Mueller.
*) Change: now the 0x00-0x1F, '"' and '\' characters are escaped as
\xXX in an access_log.
Thanks to Maxim Dounin.
*) Change: now nginx allows several "Host" request header line.
*) Feature: the "modified" flag in the "expires" directive.
*) Feature: the $uid_got and $uid_set variables may be used at any
request processing stage.
*) Feature: the $hostname variable.
Thanks to Andrei Nigmatulin.
*) Feature: DESTDIR support.
Thanks to Todd A. Fisher and Andras Voroskoi.
*) Bugfix: if sub_filter and SSI were used together, then responses
might were transferred incorrectly.
*) Bugfix: large SSI inclusions might be truncated.
*) Bugfix: the "proxy_pass" directive did not work with the HTTPS
protocol; the bug had appeared in 0.6.9.
*) Bugfix: worker processes might not catch reconfiguration and log
rotation signals.
*) Bugfix: nginx could not be built on latest Fedora 9 Linux.
Thanks to Roxis.
*) Bugfix: a segmentation fault might occur in worker process on Linux,
if keepalive was enabled.
其中关于SSI和log rotation的问题都遇到过. 回头测试一下看是否解决了我遇到的问题.
对于0.7.x的一些特性还是有兴趣的,准备近期切到开发版.
Nginx上的Memcached应用改进
由于nginx的memcached module不支持压缩, 直接缓存页面, 非常浪费内存.
实验了2个方案:
1. 直接将压缩的数据输出,并添加一个header:Content-Encoding:deflate
按说这是效率最高的,不过,很遗憾,Safari和FF都可以正常显示页面,但是**** IE, 只能显示空白页面.
2. 取消PECL内置的COMPRESS, 手动使用PHP的gzcompress函数进行压缩,这样,nginx可以通过添加header,
直接输出memcached的内容,由浏览器自行解压.
缺点: 对于不支持gzip的客户端,将无法正确获得内容. 因此,最好还是判断一下客户端是否支持gzip.这样, 下面的方案是比较好的选择.
3. 还是老办法,用perl写了memcached的fastcgi, 独立运行. 使用 IO::Uncompress::AnyInflate 解压,
并输出结果. 这个还是不错的, 使用了独立的fastcgi server的形式, 效率不低. 而且只占用一个Memcached
链接, 我也作了简单的统计, 可以监控缓存的命中率.
我使用了Memcached::libmemcached 这个CPAN module , 它使用了libmemcached 这个最近最受关注
的库. 效率比传统的Client要高效.
几个注意事项:
1. 部署时注意, 为了使用error_page进行转发, 需要 在nginx相关配置中加入:
fastcgi_intercept_errors on;
默认是off,因此, fastcgi中的404等错误将不被nginx进行重新转发.
这是我用的配置mcached.conf:
fastcgi_pass unix:/tmp/nginx-mcached.socket;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
需要缓存的location:
location /v {
set $memcached_key $scheme://$host$request_uri;
include mcached.conf;
error_page 404 = /missing$request_uri;
}
在/missing 则重写到实际的后端去.
2. PHP PECL压缩时使用了deflate, 因此需要使用inflate进行解压,不可用gunzip, 为了方便,可以用
IO::Uncompress::AnyInflate , 这样可以自动使用相应的算法.
Nginx实践 使用memcached模块加速PHP应用程序
Memcached,就不多说了,地球人都知道.
Nginx有一个memcached_module,可以直接从后端的memached服务器中读取内容,
直接输出.
通过这个模块,可以极大的提升动态页面的访问速度.
我的实践中, 曾经用这个模块快速的解决了由于代码造成的一些瓶颈问题.
memcached可以通过upstream来从多台memcached服务器中读取,也可以支持热备份.
下面是我的完整例子
upstream memcached {
server 192.168.8.11:11211;
server 192.168.8.10:11211;
}
server {
listen 80;
server_name t.night9.cn;
limit_conn one 10;
charset utf-8;
access_log /logs/web/t.log main;
root /www/app/web;
default_type text/html;
index app;
location = / {
set $memcached_key $host$uri;
default_type text/html;
memcached_pass memcached;
error_page 404 = /missing;
}
location /missing {
internal;
rewrite ^/missing$ /app/site/blog last;
}
include php_app.conf;
}
在这个例子中, nginx首先从memcached中读取(缓存的key可以用memcached_key来设置.),
如果命中,则直接输出内容,注意,需要设置default_type ,否则可能不能正常显示.
如果没有命中,memcached_module会返回404,因此可以用 error_page 404来转发到后端的应用程序,
本例中是先转发到/missing ,而/missing则重写到实际的后端应用地址中.
Nginx只负责从memcached中读取内容,但不负责设置.因此,需要你的应用程序自行将相应的页面缓存
设置到memcached中.
在我的部署中, 我是通过设置一个PAGE_CACHE_ID给php的fastcgi:
(在fastcgi_params中加入):
fastcgi_param PAGE_CACHE_ID $memcached_key;
php程序一旦检测到这个环境变量,
那么会自动将当前页面的内容设置到memcached缓存中,由于是在php框架中自动作的,所以可以随时切换,不需要修改.
缺点:
很多事情都不是那么完美, 对于nginx的这个module,其最大的问题就是不支持压缩,
因此,在使用php的memcache函数设置缓存时,必须取消压缩,否则将无法正确输出.
这样带来的一个小问题就是:
由于一般页面至少数k以上,这样对于内存的消耗是比较大.
因此, 在这个module支持压缩之前, 可以考虑备选方案:
使用独立的FASTCGI服务器来替换memcache模块.
通过CPAN模块,实现起来非常简单!
奇怪的gxip?
早上刚要出门,收到短信告知几个服务器均无法访问.
检查了一下,发现都是乱码! 明显是压缩文档的内容直接输出了.
服务器上的文档都是未压缩的, 很诡异阿.
要着急走,没有仔细看,用Safari的”网络时间线”,发现,服务器的response中赫然
出现Content-Encoding:gxip ?
明显应该是gzip,怎么会变成gxip呢? 难怪浏览器无法正确识别了.
于是,简单的禁用了相关服务器的gzip压缩, 页面就正常了.
刚回来, 为了复查这个问题,将gzip重新打开, 竟然没有重现错误!
google一下,发现也有类似的现象,但是都没有解决方案,也是莫名就好了.
由于现象出现的比较突然, 大概是早上4点多出现的.
而且是若干个服务器同时出现,也是同时好.
想了想,这几个服务器类似的地方就是都有广告,而广告服务器也在这期间down了.
初步怀疑广告输出的内容导致了nginx的相关错误(mod_gzip溢出?)
或者是HTTP劫持造成的(将gzip替换为gxip)?
将广告服务器nginx和php升级, 目前无法重现相关错误,无法做出更清晰的判断.
不知道有没有人有过相似的经历?
Nginx实践1 利用proxy_store实现高效的静态文件分布缓存服务器
曾经写过是否要放弃使用varnish/squid, 经过几天的实验,终于找到一种比较理想的解决方案:
直接使用proxy模块的proxy_store来实现分布mirror.
首先说说我的需求:
1. 我需要将一些静态文件从应用服务器剥离, 负载到其他的节点.
2. 这些文件主要是静态Html和图片,包括缩略图. 这些文件一旦创建,更新的频率很少.
3. 在某些时候需要手动立即从各个分布节点删除或更新某些文件
4. 尽可能减少应用服务器的请求, 进而减少内网的流量
之前,我分别使用了squid和varnish.
最初用的squid,还凑合.不过,squid在高负载下会出现停滞甚至crash或者是空白页.
于是换成varnish.
varnish也是老毛病,偶尔也会crash.
二者的共同点,就是当cache快满的时候,效率会急剧下降, 同时,对主服务器的请求甚至都
阻塞了整个内网.
要解决这个情况,varnish需要手动重启, squid则需要清除整个缓存目录.
对于varnish, 由于是纯内存的加速,因此,无法将cache设置太大,否则用上swap, 基本上是几倍的速度下降,
而且很容易就段违例了. 于是,当bots访问网站的时段, 就是噩梦产生的时候, 由于爬虫遍历太多的文件,
造成缓存很快溢出,于是频繁的invalid,此时,内网的带宽占用能达到100m以上….
可能有人说,为什么不用NFS. NFS的问题主要是锁的问题. 很容易造成死锁, 只有硬件重启才能解决.
为了脱离这个噩梦,我决定试验nginx的proxy_store. 如果使用Lighty,倒是非常简单,因为有mod_cache,配合lua,
会很灵活. 不过nginx的proxy_store并非是一个cache,因为它不具备expires, 新的cache模块仍在开发中.
不过经过仔细考量, 我惊喜的发现,其实这正是我想要的, 因为在我的需求中,绝大多数的文件都是不过期的,因而也无必要
去和后端服务器验证是否过期.
配置其实并不太复杂,但是过程有些曲折, 基本的思路是:
nginx首先检查本地是否有请求的文件,如果有直接送出,没有则从后端请求,并将结果存储在本地.
第一个方案,是基于error_page来实现的:
upstream backend{
server 192.168.8.10:80;
}
server {
listen 80;
access_log /logs/cache.log main;
server_name blog.night9.cn www.night9.cn night9.cn;
proxy_temp_path /cache/temp;
root /cache/$host;
location / {
index index.shtml;
error_page 404 = /fetch$uri;
}
ssi on;
location /fetch {
internal;
proxy_pass http://backend;
proxy_store on;
proxy_store_access user:rw group:rw all:rw;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Via "s9/nginx";
alias /cache/$host;
}
#对于请求目录的情况下要特殊对待
location ~ /$ {
index index.shtml;
error_page 403 404 = @fetch;
}
location @fetch {
internal;
proxy_pass http://backend;
proxy_store /cache/$host${uri}index.shtml;
proxy_store_access user:rw group:rw all:rw;
proxy_set_header Host $host;
proxy_set_header Via "s9/nginx";
proxy_set_header X-Real-IP $remote_addr;
}
}
这个方案对于普通的情况下,基本满足.
缓存是做到了,但是如何实现更新呢?
其实很简单,只要将指定url的从本地cache目录删除即可.
因为proxy_store会按照实际请求的url地址建立相应的目录结构.
于是,我写了一个fastcgi, 只要将需要清楚的url传递给它,从cache目录中删除.
其实可以用perl_module实现,但是考虑到独立fastcgi服务更为稳定,还是和以前的统计一样,
用perl的CGI::Fast模块实现, 替换了10几行代码就搞定了.
事情本来就该告一段,不过,由于主服务器上使用了SSI, 新的问题就来了:
我们希望SSI的解析是在子节点上进行,而不是在主服务器上进行, 这样我们可以独立更新相应
区块的文件即可, 否则就需要清除所有的shtml文件,这是比较可怕的.
但是,Nginx对于SSI的subrequest无法使用error_page来重定向.(不确定是否是bug,不过如果允许
的确容易造成死循环).
于是,一个更为简单的方案就诞生了:
set $index 'index.shtml';
set $store_file $request_filename;
if ($uri ~ /$ ){
set $store_file $request_filename$index;
rewrite (.*) $1index.shtml last;
}
location / {
index index.shtml;
proxy_store on;
proxy_temp_path /cache/temp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Via "s9/nginx";
proxy_store_access user:rw group:rw all:rw;
if ( !-e $store_file ) {
proxy_pass http://backend;
}
}
Wow! 更为简单.
应该感谢Nginx的Rewrite模块, 这点也是我用Nginx替换Lighttpd的一个主要原因.
好了,我可以忘掉varnish,squid了.
如果有兴趣的人想使用, 请一定注意:
这个方案对静态文件更为有效,如果要加速动态请求,还是要用varnish
说道加速, 利用Memcached和nginx配合可以迅速提升访问动态页面的速度,
有时间再说.
后记:
写这个东西源自 Fenng提到的Nginx 的推广问题
分享一下我使用Nginx的体验