本文为大家介绍nginx是如何对静态文件进行cache处理的,有兴趣的朋友可以看看。
Nginx中对静态文件进行了Cache,对应的命令就是open_file_cache,open_file_cache_min_uses以及open_file_cache_valid。这次我就来分析下nginx如何对静态文件进行cache的。
注意,open_file_cache的 inactive表示文件多久不被访问就会从cache中删除。
来看Linux下是如何做的,因为这里nginx对于bsd版本有一个不同的做法,这是因为bsd中可以给kqueue监听文件改变的事件。而linux下,nginx并没有使用Inotify,而是每次都会判断文件的st_ino来得到文件是否被修改,不过这样会有个缺点就是如果你是使用open,然后write来修改文件的话,文件其实是相同的,因此st_ino是相同的,此时nginx是无法知道的,因此修改的话,最好使用会先删除再覆盖的命令(比如cp)。
首先,nginx的cache只是cache句柄,因为静态文件的发送,一般来说,nginx都是尽量使用sendfile进行发送的,因此之需要cache句柄就够了。
所有的cache对象包含在两个数据结构里面,整个机制最关键的也是这两个东西,一个是红黑树,一个是一个队列,其中红黑树是为了方便查找(需要根据文件名迅速得到fd),而队列为了方便超时管理(按照读取顺序插入,在头的就是最近存取的文件),由于所有的句柄的超时时间都是一样的,因此每次只需要判断最后几个元素就够了,因为这个超时不需要那么实时.
假设现在客户端请求是GET test.html HTTP/1.1 ,则nginx是这么处理的,如果test.html在cache中存在,则从cache中取得这个句柄,然后正常返回,如果test.html不存在,则是打开这个文件,然后插入到cache中。不过这里有很多细节都需要处理,比如超时,比如红黑树的插入等等,接下来,我们就对照着代码来看这些都是如何处理的。
主要代码都是包含在ngx_open_cached_file中的。
首先,这里,cache的红黑树的key是一个hash值,是文件名的crc校验码:
hash = ngx_crc32_long(name->data, name->len);
//根据名字查找对应的file对象。
file = ngx_open_file_lookup(cache, name, hash);
首先我们来看如果没有找到cache对象的情况,此时nginx会open 这个文件,然后保存对应的信息到cache中。
//打开文件,保存文件信息
rc = ngx_open_and_stat_file(name->data, of, pool->log);
if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
goto failed;
}
create:
//max为open_file_cache命令中定义的那个max指令,而current也就是当前cache的文件个数
if (cache->current >= cache->max) {
//如果大于max,则需要强制expire几个元素
ngx_expire_old_cached_files(cache, 0, pool->log);
}
...........................................................
ngx_cpystrn(file->name, name->data, name->len + 1);
file->node.key = hash;
//插入到红黑树中。
ngx_rbtree_insert(&cache->rbtree, &file->node);
//更新current
cache->current++;
然后就是更新队列的部分,这里可以看到插入是每次都插入到队列的头,之所以插入到头,是为了超时操作更方便。还有一个需要注意的地方就是绑定ngx_pool_cleanup_t的handler,我们知道nginx中可以给pool绑定对应的handler,绑定后,当pool被释放的时候,就会调用这个handler,这里nginx给request pool绑定了一个handler,这里是为了处理超时的,后面我们会分析到。
//更新创建时间
file->created = now;
found:
//更新存取时间
file->accessed = now;
//将文件插入到超时队列中。
ngx_queue_insert_head(&cache->expire_queue, &file->queue);
if (of->err == 0) {
if (!of->is_dir) {
//这里很关键,将cln的handler
cln->handler = ngx_open_file_cleanup;
ofcln = cln->data;
ofcln->cache = cache;
ofcln->file = file;
ofcln->min_uses = of->min_uses;
ofcln->log = pool->log;
}
return NGX_OK;
}
return NGX_ERROR;
然后就是超时机制是如何实现的。在nginx中并没有通过定时器什么的来实现,而是通过nginx的一个特性,那就是每个request结束的时候,都会清理掉他所分配的pool,而nginx就是给每个有打开文件的request都绑定了对应clean handler,当request pool被释放的时候,就会来根据时间来判断是否已经超时,这里的clean handler就是ngx_open_file_cleanup。
static void
ngx_open_file_cleanup(void *data)
{
ngx_open_file_cache_cleanup_t *c = data;
c->file->count--;
//关闭
ngx_close_cached_file(c->cache, c->file, c->min_uses, c->log);
/* drop one or two expired open files */
//处理超时
ngx_expire_old_cached_files(c->cache, 1, c->log);
}
这里ngx_cached_open_file_s有3个域需要注意,分别是uniq,count以及close。其中uniq也就是文件属性中的st_ino(同一个设备中的每个文件,这个值都是不同的),这个值主要用于判断文件是否被修改(不过这个修改是覆盖这类的,如果你用open打开,然后写入的话,这个值还是一样的).count是文件的引用计数,表示现在文件被几个请求使用中。close表示文件是否需要被关闭(比如。
接下来就是nginx的超时处理测略了。
首先来看ngx_close_cached_file,这个函数主要是尝试关闭当前的文件,对这代码我们来看流程。
//判断是否需要关闭
if (!file->close) {
//如果不需要,则更新存取时间
file->accessed = ngx_time();
//然后将文件插入到队列头(先remove然后insert)
ngx_queue_remove(&file->queue);
ngx_queue_insert_head(&cache->expire_queue, &file->queue);
//看文件的使用次数是否大于设置的最小次数,或者文件的引用技术是否大于0,如果有一个满足,则直接返回,因为此时不需要close文件.
if (file->uses >= min_uses || file->count) {
return;
}
}
ngx_open_file_del_event(file);
if (file->count) {
return;
}
//到达这里说明文件需要被关闭。
if (file->fd != NGX_INVALID_FILE) {
//关闭文件
if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
ngx_close_file_n " \"%s\" failed", file->name);
}
file->fd = NGX_INVALID_FILE;
}
if (!file->close) {
return;
接下来是ngx_expire_old_cached_files,这个函数比较关键,主要是用来执行超时,或者强制超时cache中的元素(根据参数不同).主要的判断就是now – file->accessed <= cache->inactive,这段,这段主要是看这个文件多久没有被使用,是否超过了我们设置的inactive时间.
while (n < 3) {
//如果队列为空,则直接返回
if (ngx_queue_empty(&cache->expire_queue)) {
return;
}
//取出最后一个文件,也就是可能需要被超时的文件(因为尾部是最长时间没有操作的文件)
q = ngx_queue_last(&cache->expire_queue);
file = ngx_queue_data(q, ngx_cached_open_file_t, queue);
//n是控制是强制超时,还是按inactive超时,后一个判断是判断是否超时
if (n++ != 0 && now - file->accessed <= cache->inactive) {
return;
}
//如果有超时的,或者需要强制超时,则开始从队列和红黑树中移除
ngx_queue_remove(q);
ngx_rbtree_delete(&cache->rbtree, &file->node);
cache->current--;
ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
"expire cached open file: %s", file->name);
if (!file->err && !file->is_dir) {
//关闭文件
file->close = 1;
ngx_close_cached_file(cache, file, 0, log);
} else {
ngx_free(file->name);
ngx_free(file);
}
}
最后来看,nginx里面如何判断文件被改变的。当需要打开一个文件,然后在cache中发现了文件,此时就需要判断文件是否被改变,这里nginx只能判断删除,然后覆盖的这类,而你如果只是简单的附加,则nginx无法知晓,来看代码,在ngx_open_and_stat_file里面的。判断很简单,就是看uniq是否相等,如果不等则说明文件被改变,此时需要重新打开文件,然后重新加入cache,否则直接返回。
if (of->fd != NGX_INVALID_FILE) {
if (ngx_file_info(name, &fi) == NGX_FILE_ERROR) {
of->failed = ngx_file_info_n;
goto failed;
}
//判断文件是否被改变
if (of->uniq == ngx_file_uniq(&fi)) {
goto done;
}
}
//否则重新打开
if (!of->log) {
/*
* Use non-blocking open() not to hang on FIFO files, etc.
* This flag has no effect on a regular files.
*/
fd = ngx_open_file(name, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
NGX_FILE_OPEN, 0);
} else {
fd = ngx_open_file(name, NGX_FILE_APPEND, NGX_FILE_CREATE_OR_OPEN,
NGX_FILE_DEFAULT_ACCESS);
}
HTTP/1.1 405 Method not allowed”问题的解决方法,让静态文件响应POST请求,有需要的朋友可以参考下。
Apache、IIS、Nginx等绝大多数web服务器,都不允许静态文件响应POST请求,否则会返回“HTTP/1.1 405 Method not allowed”错误。
例1:用Linux下的curl命令发送POST请求给Apache服务器上的HTML静态页
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD>
<TITLE>405 Method Not Allowed</TITLE>
</HEAD><BODY>
<H1>Method Not Allowed</H1>
The requested method POST is not allowed for the URL /index.html.<P>
<HR>
<ADDRESS>Apache/1.3.37 Server at www.sohu.com Port 80</ADDRESS>
</BODY></HTML>
例2:用Linux下的curl命令发送POST请求给Nginx服务器上的HTML静态页
<html>
<head><title>405 Not Allowed</title></head>
<body bgcolor="white">
<center><h1>405 Not Allowed</h1></center>
<hr><center>nginx/0.5.35</center>
</body>
</html>
但在有些应用中,需要使静态文件能够响应POST请求。
对于Nginx,可以修改nginc.conf配置文件,改变“405错误”为“200 ok”,并配置location来解决,方法如下:
server
{
listen 80;
server_name domain.s135.com;
index index.html index.htm index.php;
root /opt/htdocs;
if (-d $request_filename)
{
rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent;
}
error_page 405 =200 @405;
location @405
{
root /opt/htdocs;
}
location ~ .*\.php?$
{
include conf/fcgi.conf;
fastcgi_pass 127.0.0.1:10080;
fastcgi_index index.php;
}
}
以上内容转自:张宴的博客,有简单修改。
问题描述:
系统响应很慢,界面要刷新很久才出得来。查后台也没有报什么错,我们系统是用nginx做负载均衡。惯性地不走负载均衡而直接访问单节点应用,发现响应很快,很正常。初步定位问题出在nginx上,然后查nginx日志,发现有很多错误,错误中有“13: Permission denied”这个信息,明显是权限问题,很奇怪,之前运行都很正常啊。后来一问才知道,维护人员做了操作。
系统上nginx安装时使用的是root用户,也是用root用户启动的,所以要修改配置的时候需要使用root用户,管理上不方便,所以维护人员心血来潮修改了nginx的权限(后来知道他是使用这个命令修改的权限chown -R user:group $nginxdir)。就是将nginx的用户和组都换掉了,但是这样为什么会造成“响应慢”呢?
原因分析及解决方法:
在linux上有些应用程序的一些进程会默认使用nobody这个用户来启动,以保安全。nginx有两种进程,除主进程之外的工作进程都是用nobody这个用户启动的(nginx工作进程的数量使用worker_processes这个参数来设定)。而工作进程要访问nginx下这两个目录client_body_temp和proxy_temp(这两个目录按我的理解是缓存一些静态文件,比如图片或者css文件什么的,以提高nginx访问速度),权限变更后,造成工作进程访问不了这两个目录下的内容,造成某些图片和连接打不开,就像响应很慢一样。将权限变更一下就OK了。
附:
linux中的nobody用户
nobody是系统用户,是一个不能登陆的帐号,一个特殊用途的用户 ID ,一些服务进程如apache,aquid等都采用一些特殊的帐号来运行,比如nobody,news,games等等。一般来说 uid < 500 的都是系统 ID 。
Linux 系统为了安全,很多操作和服务的运行都不是运行在 root 用户下面的,而是一个专用的 ID ,这个 ID 一般就是 nobody ,这样就可以把每个服务运行的情况隔离出来。保证不会因为服务器程序的问题而让服务器程序成了黑客的直接操作源(黑客拿下了服务器程序,也仅仅是 nobody 用户而不是 root 用户)。同时也不会影响其他用户的数据。
服务器程序提权有专用的办法来防止恶意使用的。
除了 nobody ,常见的还有 ftp 、ssh 什么的。有的不是用来跑服务,而是用来占坑,主要是用用户组的权限管理进行权限设置,这个时候会有一个占坑用的同名 ID 加入到用户组。这种情况好像主要是为了兼容。