分类目录归档:nginx

nginx lua 模块的一个bug

近期协助排查一个故障发现有50x错误,开error日志发现有malloc或者crash work process的记录

[emerg] 5309#0: *288 malloc(808334101) failed (12: Cannot allocate memory) while sending to client
[notice] 4579#0: signal 18 (SIGCHLD) received
[notice] 4579#0: worker process 5211 exited with code 0

这其实是lua模块导致的问题,在0.7.5之前,nginx lua module存在 ngx.req.clear_header 导致的内存溢出问题,需要升级到最新版
that is all,完毕

nginx gunzip filter 模块

介绍下igor职业打手Maxim Dounin写的一个gunzip模块

Gunzip module for nginx.
This module allows gunzipping responses returned with Content-Encoding: gzip
for clients that doesn’t support it. It may be usefull if you prefer to store
data compressed (to save space or disk/network IO) but do not want to penalize
clients without gzip support.

Note well: only responses with Content-Encoding set to gzip before this module
are handled (e.g. using “add_header Content-Encoding gzip;” isn’t enough as it
happens after). As of now only proxy and fastcgi are able to do so.

这个模块能针对不支持gzip编码的客户端,直接解压gzip格式的内容
好处:
1.跟源站直接请求压缩的内容,减少回源带宽,提高响应速度
2.只保留一份压缩的内容,减少缓存的大小,相同的cache能放更多的内容
配置格式比较简单:

Configuration directives:

gunzip (on|off)

Context: http, server, location
Default: off

Switches gunzip.

gunzip_buffers

Context: http, server, location
Default: 32 4k/16 8k

Specifies number and size of buffers available for decompression.

Usage:

location /storage/ {
gunzip on;

}

需要特别指出:
1.客户端不支持gzip编码,那么gunzip模块就返回解压的内容
2.客户端支持gzip编码,那么gunzip就自动不起作用,返回原始内容

某些特殊需求,比如addition_filter,我们知道这些filter在非压缩的内容才能正常工作,要结合这个模块就不是那么方便了
因此可以简单修改代码,让它不理会客户端的header,总是返回非压缩内容,注释掉这一整块即可:
ngx_http_gunzip_filter_module.c

141 #if (nginx_version >= 8025 || (nginx_version >= 7065 && nginx_version < 8000)) 142 143 r->gzip_vary = 1;
144
145 if (!r->gzip_tested) {
146 if (ngx_http_gzip_ok(r) == NGX_OK) {
147 return ngx_http_next_header_filter(r);
148 }
149
150 } else if (!r->gzip_ok) {
151 return ngx_http_next_header_filter(r);
152 }
153
154 #else
155
156 if (ngx_http_gzip_ok(r) == NGX_OK) {
157 return ngx_http_next_header_filter(r);
158 }
159
160 #endif

有人写了个gunzip_always的开关补丁,可以控制这个属性,patch暂时不放出来

模块地址: http://mdounin.ru/hg/ngx_http_gunzip_filter_module/

nginx ssl https 设置

本文已经过时并且存在大量不安全,请参阅最新文档   https://www.4os.org/index.php/category/https/
nginx 默认编译就是支持https的,只需要开启ssl就好
配置如下:
                listen                  443 ;
                server_name             4os.org *.4os.org;

                ssl                     on;
                ssl_certificate         gz.crt;
                ssl_certificate_key     gz.key;
                ssl_session_timeout     5m;
                ssl_protocols           SSLv2 SSLv3 TLSv1;
                ssl_ciphers             ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
                ssl_prefer_server_ciphers       on;
....

其中:
gz.crt是证书颁发机构给的证书(免费ssl-证书/)
gz.key是解密后的私钥

上文中提到的私钥是加密的,可以在startssl的工具箱里边解密,也可以自己做:

openssl  rsa -in gz.pri -out gz.key,输入私钥生成时设置的密码,出来的就是不加密的私钥了,nginx启动也不会要你输入密码了

补充:

1. “SSL_CTX_use_PrivateKey_file fail”之类的错误,通常都是私钥和证书不匹配造成的,请确认你生成证书与私钥匹配

2.firefox证书需要根证书信息:

wget http://www.startssl.com/certs/sub.class1.server.ca.pem

cat sub.class1.server.ca.pem >> gz.crt

停止和重启:nginx与apache的不同

停止和重启apache与nginx有些许不同,彼此经验不能照搬

TERM:

两者相同,都是发指令给父进程,父进程立刻尝试杀死所有的子进程并退出

USR1:

nginx的文档说得很简单:reopen the logfile,实际上的操作是master重新打开日志文件,并改变日志文件权限,是worker进程有读写权限,然后发USR1给worker进程重新打开日志文件,这完全不涉及任何worker进程的重新启动

apache则不同,它的父进程会”建议”所有子进程完成当前请求后退出,父进程将重新读取配置文件和日志文件,每个子进程退出后父进程将生成新的子进程

HUP:

nginx收到这个信号会做3个事情:

1.重新读取配置文件

2.使用新的配置启动新的worker进程

3.旧的worker完成当前请求后退出

apache的做法不同,父进程接收到该信号后会跟TERM信号杀掉所有子进程,重新读取配置文件,重新打开日志文件,并声称新的子进程来服务.与TERM信号不同的是,父进程不退出,服务不会中止

nginx php 缓存

这边一直有个php cache的应用,原理是发起一个对自身的请求,保存成静态文件
之前这个应用在apache worker模式跑得很好,换到nginx的fast-cgi 后一直不是很正常

这个问题跟spawn-fcgi的工作原理有关: spawn-fcgi起N个进程,然后FIFO排队处理请求
当有并发>N个PHP CACHE的应用请求过来的时候,php cache的应用会再对自身发起请求,这个请求排在了这些请求的后边,而实际上不会有新的进程来处理这些请求,从而形成死锁,日志中会充斥”upstream timed out” “no live upstreams”

因此,工作模式的排队理论本身决定了这个故障不可避免

解决办法:使用nginx proxy_cache 绕开这个问题,把php cache这个功能交给proxy_cache来完成,nginx的work process遇到阻塞等待的情况,会把这个请求的工作sleep一段时间处理接下来的请求,这个特点明显优于spawn-fcgi的行为

1.建立 /cache/打头的可缓存php location

location ~* ^/cache/(.*\.php)${

}

2. 设置相关的proxy_cache:

1) CACHEZONE:

proxy_cache_path /dev/shm/cache/app.gd.sohu.com levels=1:2 keys_zone=default:100m max_size=4g inactive=20m;

2) CACHE目录:

proxy_read_timeout 5s;
proxy_connect_timeout 5s;
proxy_set_header Host $host;
proxy_cache_use_stale updating;
proxy_cache_key “$host$uri$is_args$args”;
proxy_cache default;

proxy_ignore_headers “Cache-Control”;
proxy_hide_header “Cache-Control”;

proxy_ignore_headers “Expires”;
proxy_hide_header “Expires”;

proxy_hide_header “Set-Cookie”;
proxy_ignore_headers “Set-Cookie”;

add_header Cache-Control max-age=60;

proxy_cache_valid 200 3m;
proxy_cache_valid any 0m;
proxy_temp_path /dev/shm/cache/tmp;

proxy_pass http://serverIP/$1$is_args$args;

3) PURGE设置:

location ~* ^/purge/(.*\.php)$ {
access_log logs/purge_app_access.log sohu;

allow 10.10.0.0/16;
deny all;
set $purge_key “$host/$1$is_args$args”;
proxy_cache_purge default $purge_key;

}

3.需要注意的地方:

1) 影响动态应用能否cache的有几个header:一般是”X-Accel-Redirect”, “X-Accel-Expires”, “Expires” or “Cache-Control” ,跟某些文档提到的cookie是没有关系的,我这里根据自己的应用ignore了 “Expires” 和”Cache-Control” 的header
2) 需要注意,php使用cookie(session也是cookie header实现的一种)来识别用户,如果是私密应用,需要把cookie加入cachekey里边,开放式应用则不必如此
3)设置完毕后可以在访问路径的前边加上/cache/实现php内容的缓存,再加上/purge/就能清除其缓存
4) 需要特别说明,我这边的版本是0.7系列,从测试看1.x系列是会forward cookie的,所以需要抹掉:

proxy_hide_header Set-Cookie;
proxy_ignore_headers Set-Cookie;

2022.07补充

By default, NGINX respects other directives in the Cache-Control header: it does not cache responses when the header includes the PrivateNo-Cache, or No-Store directive. It also doesn’t cache responses with the Set-Cookie header. Further, it only caches responses to GET and HEAD requests. You can override these defaults as described in the answers below.

nginx limit zone and limit requests

今天刚好有人提起limit zone 和limit request 两个模块的问题,这里就顺便讲讲个人的一些看法:

1.limit zone:

This module makes it possible to limit the number of simultaneous connections for the assigned session or as a special case, from one address.

这个模块能针对指定的session限制并发连接数,比如限制来源地址这个特别例子

http {
limit_zone one $binary_remote_addr 10m;

server {
location /download/ {
limit_conn one 1;
}
}
}

这个配置针对 $binary_remote_addr 这个对象做了个10M的limitzone(limitzone用于存储这些变量用于判断是否超过了limit限制,这里是并发1)

2.limit request:

This module allows you to limit the number of requests for a given session, or as a special case, with one address

这个模块允许你限制指定session的请求数量,比如限制来源地址这个特别例子

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

server {
location /search/ {
limit_req zone=one burst=5;
}

看模块的说明不好懂,看这个配置就明白了,它是控制请求的频率,而不是并发的数量,单个IP burst并发进入请求是5,每秒请求限制是1

需要补充说明的是,这里的例子都是以来源IP来设置的,并非只能使用该变量.
其实指定设置其他的变量,也有不同的效果,比如limitzone,针对localtion 设置个静态变量,就相当于设置了某个路径的总体并发访问量了

朋友测试的报告地址是这个:http://storysky.blog.51cto.com/628458/642970
这个报告有什么问题呢:
1. 首先测试使用的是ab和test.php,这会导致无法正确的看到请求的结果(可以看到测试结果的503和200无规律分布)
2. 把”看来也不一定能限制的住1秒钟1个并发连接”和并发1个链接搞混了,limitzone是并发,不是频率,测试文件的size太小会导致看不出问题
因此,我这边做了个简单的DEMO说明下吧:
1. 测试文件使用了个3.9M的文件,避免第一个请求迅速完成导致并发数无法精确达到预期
2. 对location 限速,limit_rate 10k,理由也同上
测试情况:
1.第一个请求发起:

curl -H”Referer:http://www.sohu.com/” http://www.4os.org/video/other/bnx2.tar.gz -o /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
4 3927k 4 168k 0 0 11283 0 0:05:56 0:00:15 0:05:41 11931^C

2.在第一个请求未完成的时候发起第二个请求:毫无疑问的503了

curl -H”Referer:http://www.sohu.com/” http://www.4os.org/video/other/bnx2.tar.gz
503 Service Temporarily Unavailable…

至于limitRequests的测试和说明,那篇文章说得挺好的,burst的解释我也是看了之后才清楚的,表示感谢

PS.最近该把http://wiki.nginx.org/HttpLimitReqModulehttp://wiki.nginx.org/HttpLimitZoneModule
这两个模块的中文WIKI补上…

nginx 视频服务器 架设

1.web服务器选用,建议使用0.8的稳定版

2.nginx默认自带了flv解码,编译的时候加上参数–with-http_flv_module即可

3.比较流行的mp4编码,需要另外安装第三方模块,建议使用http://h264.code-shop.com的H246方案

这个模块最新版本是2.3.2(不是官网介绍的2.2.7),并且需要打patch fix两个bug

1)zero_in_uri在0.8系列取消

2)404  错误返回415

模块可以从 http://h264.code-shop.com/download/nginx_mod_h264_streaming-2.3.2.tar.gz 下载

补丁可以从   http://www.4os.org/video/nginx/h246_stream_2.3.2.patch 下载

(不建议使用2.2.7版本,该版本有fclose空指针漏洞,并根据迅雷给我的反馈看存在编码兼容性造成的高负载问题)

4. 编译参数,简单示例下:

./configure –with-http_flv_module  –add-module=/path_to/nginx_mod_h264_streaming-2.3.2

如果不想自己patch,这里有patch好的模块,一样可用:

h264_stream_2.3.2.tar.gz

5.防盗链/限速/IO优化等,将在后续详细讲解:

1)防盗链:防盗链分为简单的refer引用判断和较为复杂的动态链接两种(比如时间/文件名双因子加密)

2)限速:nginx天然的支持了限速,limit-rate参数不仅仅能让视频流平滑的输出,还能极大的缓解IO压力

3)IO优化:包括内核调整/磁盘raid设置/使用SSD设备,在具备优秀开发人员的情况下甚至可以打散磁盘放置多份热点等

any question,mail to favortel#qq.com

Nginx启动初始化过程(转载)

main函数做的第一件事情就是对参数选项进行处理,和普通的Linux程序如出一辙,如下:

if (ngx_get_options(argc, argv) != NGX_OK) { return 1; }

Nginx用此函数对参数选项进行解析,从而采取相应的动作,比如:显示版本、测试配置等功能。其实此函数实现的很简陋,远没有Linux提供的getopt()那么强悍,但却可以达到跨平台的目的。

     ngx_time_init();
#if (NGX_PCRE)
    ngx_regex_init();
#endif
    ngx_pid = ngx_getpid();
    log = ngx_log_init(ngx_prefix);
    if (log == NULL) {
        return 1;
    }

上述几行代码的功能如其名,主要完成Nginx在时间和日志等方面的初始化工作。

    if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK){
        return 1;
    }

将命令行参数保存到ngx_os_argv、ngx_argc以及ngx_argv这几个全局的变量中。这算是一个备份存储,方便以后master进程做热代码替换之用。

     if (ngx_os_init(log) != NGX_OK) {
        return 1;
    }

完成操作系统的一些信息获取,如内存页面大小、系统限制资源等信息;所有的这些资源都将会被保存在对应的全局变量中,因此后续访问将会很便利。

    if (ngx_crc32_table_init() != NGX_OK) {
        return 1;
    }

初始化一个做循环冗余校验的表,由此可以看出后续的循环冗余校验将采用高效的查表法。crc算法此处就不做分析,网上一堆一堆的相关资料,有兴趣的同学可以了解。

    if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
        return 1;
    }

通过环境变量NGINX完成socket的继承,继承来的socket将会放到init_cycle的listening数组中。在NGINX环境变量中,每个socket中间用冒号或分号隔开。完成继承同时设置全局变量ngx_inherited为1。

    ngx_max_module = 0;
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = ngx_max_module++;
    }

额!!!这个循环中的ngx_modules数组好像没见定义,难不成是火星来的?当然不是,如其名,这就是一个存储所有模块的信息,包括自己开发的模块都会放到这个数组中,而这个神秘的数组却是在自动编译的时候生成的,位于objs/ngx_modules.c文件中。这个循环的目的是清晰可见的——对所有模块进行索引编号,方便以后访问;同时借助ngx_max_module对所有模块进行了一次点数,确定究竟有多少模块。而神秘数组ngx_modules的长相大概如下:

ngx_module_t *ngx_modules[] = {
    &ngx_core_module,
    &ngx_errlog_module,
    &ngx_conf_module,
    &ngx_events_module,
    &ngx_event_core_module,
    &ngx_epoll_module,
    &ngx_openssl_module,
    &ngx_http_module,
    。。。
};
    cycle = ngx_init_cycle(&init_cycle);

这里将会初始化很多的东东到全局变量cycle中,是Nginx启动初始化的核心之处。ngx_init_cycle函数的过程比较多,放下一篇blog中逐段分析。

    if (ngx_init_signals(cycle->log) != NGX_OK) {
        return 1;
    }

注册一堆信号处理程序,需要注册的信号及相应的信号处理函数被放在一个类型为ngx_signal_t的数组signals中。数组定义在src/os/unix/ngx_process.c中。ngx_signal_t结构类型定义了信号值,信号名字,信号对应动作名以及信号处理函数。

     if (!ngx_inherited && ccf->daemon) {
        if (ngx_daemon(cycle->log) != NGX_OK) {
            return 1;
        }
        ngx_daemonized = 1;
    }

ngx_daemon肯定就是用来实现守护进程的函数了,此处就不多废话了,有需要写server程序的,可以直接copy这段代码实现守护进程。

    if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
        return 1;
    }

玩过Nginx的人都知道,Nginx启动后有一个记录进程id的文件,这个文件里面就一个pid。原来这个pid就是在这个地方记录下来的。查看ngx_create_pidfile函数可以看到这样的一行代码

if (ngx_process > NGX_PROCESS_MASTER) {
return NGX_OK;
},这行代码就说明了,不是master进程时,就不创建这样的一个pid文件。

    if (ngx_process == NGX_PROCESS_SINGLE) {
        ngx_single_process_cycle(cycle);
    } else {
        ngx_master_process_cycle(cycle);
    }

到此就基本完成Nginx的启动初始化过程了,即将开始进程相关的工作了,这里最重要的ngx_master_process_cycle这个过程,在这个过程里实现了master-worker模式的进程模型,也是生成环境下Nginx的常用模型。既然此处已不再是初始化工作,那么就留到后续分析吧。

上述所有过程都是在main函数里完成的,下一篇分析ngx_init_cycle函数。

From: http://www.tbdata.org/archives/1092

nginx core dump trace

问题: 如何core dump

答案: 配置方法如下

1. 配置nginx跑在非daemon和非master_process模式,配置样例

daemon off;

master_process off;

2. 配置worker_processes个数,可设置为1

worker_processes 1;

3. 启动nginx

ulimit -c unlimited;

/opt/soft/nginx/sbin/nginx -c /opt/soft/nginx/conf/nginx.conf;

4. 阅读coredump

gdb sbin/nginx  -c core.$pid$

gdb>where

#貌似说最新的coredump办法,不影响在线业务

nginx配置方法
worker_rlimit_core  500M;
working_directory   /path/to/cores/;
要保证nginx进程用户对working_directory可写
原来的配置都不用动,也跑在daemon模式下,有master进程
只需要增加这两行就成了
worker_rlimit_core限制生成core大小的,如果占内存多就搞大
确认需要以下方式:
#新建一个文件夹, 并确认nginx可以读写
$ mkdir /tmp/cores
$ sudo chown root:root /tmp/cores
$ sudo chmod 1777 /tmp/cores
#设置unlimited core file dump
$ ulimit -c unlimited
#设置系统级别的core file
$ echo "/tmp/cores/core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern
#允许suid dumpable
$ sudo sysctl -w fs.suid_dumpable=2
$ sysctl -p
可以发送信号, 让nginx 直接生成coredump, 测试以上配置是否生效
比如kill -11 $work_process_pid