1. Nginx 的安装
1.1 安装依赖
1.2 编译Nginx(结合Lua 模块)
注: Lua 模块在Nginx1.4以上版本兼容不太文档
1.2.1 下载安装LuaJit(2.0/2.1)并安装
下载地址
编辑 Makefile文件, 找到ldconfig位置(75行)
原内容是: LDCONFIG= ldconfig -n
修改为: LDCONFIG= /sbin/ldconfig -n
make --PREFIX=/usr/local/luajit && make intall
1.2.2 下载 ngx_devel_kit (NDK) 及 ngx_lua
ngx_devel_kit
lua-nginx-module
1.2.3 编译安装NGINX
设置变量:
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0
./configure --prefix=/usr/local/nginx \
--with-stream --with-http_ssl_module --with-http_flv_module \
--with-http_gzip_static_module --with-http_stub_status_module \
--with-openssl=/home/download/openssl-1.1.1-pre9 \
--http-log-path=/var/log/nginx/access.log \
--http-client-body-temp-path=/var/tmp/nginx/client \
--http-proxy-temp-path=/var/tmp/nginx/proxy \
--http-fastcgi-temp-path=/var/tmp/nginx/fcgi \
--with-ld-opt="-Wl,-rpath,/usr/local/luajit/lib" \
--add-module=/home/download/ngx_devel_kit-0.3.1rc1 \
--add-module=/home/download/lua-nginx-module-0.10.12
2. 环境设置
2.1 Nginx 用户组
groupadd www-data
useradd -g www-data www-data
2.2 设置介绍
nginx 配置文件主要分成四个部分:
- main, 全局设置, 影响其它部分所有设置
- server, 主机服务相关设置, 主要用于指定虚拟主机域名、IP和端口
- location, URL匹配特定位置后的设置, 反向代理、内容篡改相关设置
- upstream, 上游服务器设置, 负载均衡相关配置
他们之间的关系式: server继承main, location继承server; upstream既不会继承指令也不会被继承。
2.3 Nginx Https 设置
http {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
server {
listen 443 ssl;
server_name example.com;
keepalive_timeout 70;
access_log /var/log/xxx.log;
root /xxx/xxx/;
#证书在服务器的绝对位置
ssl_certificate /xxx/213996598310998.pem;
ssl_certificate_key /xxx/213996598310998.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
}
}
2.4 日志按月滚动
vim /etc/logrotate.d/nginx
/data/logs/nginx/*.log {
missingok
dateext
dateformat -%Y%m%d
notifempty
monthly
rotate 7
size 200M
sharedscripts
postrotate
if [ -f /usr/local/nginx/logs/nginx.pid ]; then
kill -USR1 `cat /usr/local/nginx/logs/nginx.pid`
fi
endscript
}
crontab -e #添加以下代码
0 0 1 * * /usr/sbin/logrotate -vf /etc/logrotate.d/nginx #每天凌晨定时执行脚本
2.5 worker_processes与worker_connections
2.5.1 worker_processes
worker角色的进程个数(nginx启动后有多少个worker处理http请求。master不处理请求, 而是根据相应配置文件信息管理worker进程. master进程主要负责对外揽活(即接收客户端的请求), 并将活儿合理的分配给多个worker, 每个worker进程主要负责干活(处理请求))。
最理想的worker_processes值取决于很多因素, 包含但不限于CPU的核数, 存储数据的硬盘驱动器个数(跟这个有什么关系?难道和cpu一样, 存在跨区域读取数据问题), 以及负载模式(?这个是什么?)当其中任何一个因素不确定的时候, 将其设置为cpu核数或许是一个比较好的初始值, “自动”也基本是如此确认一个参数值的。
“自动”这个参数值是从nginx 1.3.8和nginx 1.2.5 开始进行支持的, 自动参数可以自动检测 cpu cores 并设置 worker_processes 参数 。
如果nginx处理的是cpu密集型(比较耗费cpu的)的操作, 建议将此值设置为cpu个数或cpu的核数。
2.5.2 worker_connections
官方解释如下, 个人认为是每一个worker进程能并发处理(发起)的最大连接数(包含所有连接数)。
不能超过最大文件打开数: 在linux终端中输入ulimit -a进行查看
2.6 杂项
# 隐藏Nginx版本信息:
server_tokens off;
3. Nginx模型
3.1 进程
Nginx採用多进程模型, 单Master—多Worker, 由Master处理外部信号、配置文件的读取及Worker的初始化。Worker进程採用单线程、非堵塞的事件模型(Event Loop, 事件循环)来实现port的监听及client请求的处理和响应, 同一时候Worker还要处理来自Master的信号。
因为Worker使用单线程处理各种事件。所以一定要保证主循环是非堵塞的, 否则会大大减少Worker的响应能力。
3.2 协程(Coroutine)
协程类似一种多线程, 与多线程的差别有:
-
- 协程并不是os线程, 所以创建、切换开销比线程相对要小。
-
- 协程与线程一样有自己的栈、局部变量等, 可是协程的栈是在用户进程空间模拟的, 所以创建、切换开销非常小。
-
- 多线程程序是多个线程并发运行。也就是说在一瞬间有多个控制流在运行。而协程强调的是一种多个协程间协作的关系, 仅仅有当一个协程主动放弃运行权, 还有一个协程才干获得运行权, 所以在某一瞬间, 多个协程间仅仅有一个在运行。
-
- 因为多个协程时仅仅有一个在执行, 所以对于临界区的訪问不须要加锁。而多线程的情况则必须加锁。
-
- 多线程程序因为有多个控制流。所以程序的行为不可控, 而多个协程的运行是由开发人员定义的所以是可控的。
Nginx的每一个Worker进程都是在epoll或kqueue这种事件模型之上, 封装成协程, 每一个请求都有一个协程进行处理。这正好与Lua内建协程的模型是一致的, 所以即使ngx_lua须要运行Lua, 相对C有一定的开销, 但依旧能保证高并发能力。
- 多线程程序因为有多个控制流。所以程序的行为不可控, 而多个协程的运行是由开发人员定义的所以是可控的。
3.3 负载均衡
- 1、轮询, 即Round Robin, 根据Nginx配置文件中的顺序, 依次把客户端的Web请求分发到不同的后端服务器。
- 2、最少连接
- 3、IP地址哈希
- 4、基于权重的负载均衡
3.4 子请求(subrequest)
事实上在Nginx 世界里有两种类型的“请求”。一种叫做“主请求”(main request), 而还有一种则叫做“子请求”(subrequest)。 所谓“主请求”。就是由 HTTP client从 Nginx 外部发起的请求。比方。从浏览器訪问Nginx就是一个“主请求”。 而“子请求”则是由 Nginx 正在处理的请求在 Nginx 内部发起的一种级联请求。“子请求”在外观上非常像 HTTP 请求, 但实现上却和 HTTP 协议乃至网络通信一点儿关系都没有。它是 Nginx 内部的一种抽象调用, 目的是为了方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”, 并发或串行地訪问多个 location 接口。然后由这些 location 接口通力协作, 共同完毕整个“主请求”。当然。“子请求”的概念是相对的, 不论什么一个“子请求”也能够再发起很多其它的“子子请求”。甚至能够玩递归调用(即自己调用自己)。
当一个请求发起一个“子请求”的时候, 依照 Nginx 的术语, 习惯把前者称为后者的“父请求”(parent request)。
location /main {
echo_location /foo; # echo_location发送子请求到指定的location
echo_location /bar;
}
location /foo {
echo foo;
}
location /bar {
echo bar;
}
$ curl location/main
$ foo 03. bar
这里, main location就是发送2个子请求, 分别到foo和bar。这就类似一种函数调用。
“子请求”方式的通信是在同一个虚拟主机内部进行的。所以 Nginx 核心在实现“子请求”的时候, 就仅仅调用了若干个 C 函数, 全然不涉及不论什么网络或者 UNIX 套接字(socket)通信。我们由此能够看出“子请求”的运行效率是极高的。
4. Nginx处理Http请求的过程
表面上看, 当Nginx处理一个来自client的请求时, 先依据请求头的host、ip和port来确定由哪个server处理, 确定了server之后, 再依据请求的uri找到相应的location。这个请求就由这个location处理。
实际Nginx将一个请求的处理划分为若干个不同阶段(phase)。这些阶段依照前后顺序依次运行。也就是说NGX_HTTP_POST_READ_PHASE在第一个, NGX_HTTP_LOG_PHASE在最后一个。
NGX_HTTP_POST_READ_PHASE, //0读取请求phase
NGX_HTTP_SERVER_REWRITE_PHASE, //1这个阶段主要是处理全局的(server block)的rewrite
NGX_HTTP_FIND_CONFIG_PHASE, //2这个阶段主要是通过uri来查找相应的location, 然后依据loc_conf设置r的相应变量
NGX_HTTP_REWRITE_PHASE, //3这个主要处理location的rewrite
NGX_HTTP_POST_REWRITE_PHASE, //4postrewrite, 这个主要是进行一些校验以及收尾工作。以便于交给后面的模块。
NGX_HTTP_PREACCESS_PHASE, //5比方流控这样的类型的access就放在这个phase, 也就是说它主要是进行一些比較粗粒度的access。
NGX_HTTP_ACCESS_PHASE, //6这个比方存取控制, 权限验证就放在这个phase, 一般来说处理动作是交给以下的模块做的.这个主要是做一些细粒度的access
NGX_HTTP_POST_ACCESS_PHASE, //7一般来说当上面的access模块得到access_code之后就会由这个模块依据access_code来进行操作
NGX_HTTP_TRY_FILES_PHASE, //8try_file模块, 就是相应配置文件里的try_files指令。可接收多个路径作为參数。当前一个路径的资源无法找到, 则自己主动查找下一个路径
NGX_HTTP_CONTENT_PHASE, //9内容处理模块
NGX_HTTP_LOG_PHASE //10log模块
每一个阶段上能够注冊handler。处理请求就是执行每一个阶段上注冊的handler。Nginx模块提供的配置指令仅仅会一般仅仅会注冊并执行在当中的某一个处理阶段。
比方, set指令属于rewrite模块的, 执行在rewrite阶段, deny和allow执行在access阶段。
5. Nginx 的 TCP 负载均衡
Nginx除了以前常用的HTTP负载均衡外, Nginx增加基于TCP协议实现的负载均衡方法。
HTTP负载均衡, 也就是我们通常所有“七层负载均衡”, 工作在第七层“应用层”。而TCP负载均衡, 就是我们通常所说的“四层负载均衡”, 工作在“网络层”和“传输层”。例如, LVS(Linux Virtual Server, Linux虚拟服务)和F5(一种硬件负载均衡设备), 也是属于“四层负载均衡”。
5.1 TCP负载均衡的配置方式
Nginx使用了一个新的stream模块来实现TCP负载均衡, 这个模块, 类似于http和mail模块, 允许我们配置一组监听TCP连接的服务。允许你配置多个服务的TCP连接, 通过在upstream的server组中配置proxy_pass指令。
修改nginx.conf文件, 在http模块的统计目录, 添加一个stream模块(和http等同级):
stream {
server {
listen 1034;
proxy_pass app;
}
upstream app {
server 192.168.0.3:1034;
server 192.168.0.4:1034;
server 192.168.0.6:1034;
}
}
5.2 TCP负载均衡的执行原理
当Nginx从监听端口收到一个新的客户端链接时, 立刻执行路由调度算法, 获得指定需要连接的服务IP, 然后创建一个新的上游连接, 连接到指定服务器。
TCP负载均衡支持Nginx原有的调度算法, 包括Round Robin(默认, 轮询调度), 哈希(选择一致)等。同时, 调度信息数据也会和健壮性检测模块一起协作, 为每个连接选择适当的目标上游服务器。如果使用Hash负载均衡的调度方法, 你可以使用$remote_addr(客户端IP)来达成简单持久化会话(同一个客户端IP的连接, 总是落到同一个服务server上)。
和其他upstream模块一样, TCP的stream模块也支持自定义负载均和的转发权重(配置“weight=2”), 还有backup和down的参数, 用于踢掉失效的上游服务器。max_conns参数可以限制一台服务器的TCP连接数量, 根据服务器的容量来设置恰当的配置数值, 尤其在高并发的场景下, 可以达到过载保护的目的。
Nginx监控客户端连接和上游连接, 一旦接收到数据, 则Nginx会立刻读取并且推送到上游连接, 不会做TCP连接内的数据检测。Nginx维护一份内存缓冲区, 用于客户端和上游数据的写入。如果客户端或者服务端传输了量很大的数据, 缓冲区会适当增加内存的大小。
当Nginx收到任意一方的关闭连接通知, 或者TCP连接被闲置超过了proxy_timeout配置的时间, 连接将会被关闭。对于TCP长连接, 我们更应该选择适当的proxy_timeout的时间, 同时, 关注监听socke的so_keepalive参数, 防止过早地断开连接。
5.3 服务健壮性监控
TCP负载均衡模块支持内置健壮性检测, 一台上游服务器如果拒绝TCP连接超过proxy_connect_timeout配置的时间, 将会被认为已经失效。在这种情况下, Nginx立刻尝试连接upstream组内的另一台正常的服务器。连接失败信息将会记录到Nginx的错误日志中。
如果一台服务器, 反复失败(超过了max_fails或者fail_timeout配置的参数), Nginx也会踢掉这台服务器。服务器被踢掉60秒后, Nginx会偶尔尝试重连它, 检测它是否恢复正常。如果服务器恢复正常, Nginx将它加回到upstream组内, 缓慢加大连接请求的比例。
之所“缓慢加大”, 因为通常一个服务都有“热点数据”, 也就是说, 80%以上甚至更多的请求, 实际都会被阻挡在“热点数据缓存”中, 真正执行处理的请求只有很少的一部分。在机器刚刚启动的时候, “热点数据缓存”实际上还没有建立, 这个时候爆发性地转发大量请求过来, 很可能导致机器无法“承受”而再次挂掉。以mysql为例子, 我们的mysql查询, 通常95%以上都是落在了内存cache中, 真正执行查询的并不多。
其实, 无论是单台机器或者一个集群, 在高并发请求场景下, 重启或者切换, 都存在这个风险, 解决的途径主要是两种:
(1)请求逐步增加, 从少到多, 逐步积累热点数据, 最终达到正常服务状态。
(2)提前准备好“常用”的数据, 主动对服务做“预热”, 预热完成之后, 再开放服务器的访问。
TCP负载均衡原理上和LVS等是一致的, 工作在更为底层, 性能会高于原来HTTP负载均衡不少。但是, 不会比LVS更为出色, LVS被置于内核模块, 而Nginx工作在用户态, 而且, Nginx相对比较重。另外一点, 令人感到非常可惜, 这个模块竟然是个付费功能。(补注: 本文写于 2015 年 1 月, 当初这个模块是收费的)
6. 常见问题
6.1 常用的全局变量
nginx变量 | 作用 |
---|---|
$arg_PARAMETER | 这个变量包含GET请求中, 如果有变量PARAMETER时的值 |
$args | 这个变量等于请求行中(GET请求)的参数, 例如foo=123&bar=blahblah |
$binary_remote_addr | 二进制的客户地址 |
$body_bytes_sent | 响应时送出的body字节数数量。即使连接中断, 这个数据也是精确的。 |
$content_length | 请求头中的Content-length字段。 |
$content_type | 请求头中的Content-Type字段。 |
$cookie_COOKIE | cookie COOKIE变量的值 |
$document_root | 当前请求在root指令中指定的值 |
$document_uri | 与$uri相同 |
$host | 请求主机头字段, 否则为服务器名称 |
$hostname | Set to the machine’s hostname as returned by gethostname |
$is_args | 如果有$args参数, 这个变量等于”?”, 否则等于”", 空值 |
$http_user_agent | 客户端agent信息 |
$http_cookie | 客户端cookie信息 |
$limit_rate | 这个变量可以限制连接速率 |
$query_string | 与$args相同 |
$request_body_file | 客户端请求主体信息的临时文件名 |
$request_method | 客户端请求的动作, 通常为GET或POST |
$remote_addr | 客户端的IP地址 |
$remote_port | 客户端的端口 |
$remote_user | 已经经过Auth Basic Module验证的用户名 |
$request_completion | 如果请求结束, 设置为OK. 当请求未结束或如果该请求不是请求链串的最后一个时, 为空(Empty) |
$request_method | GET或POST |
$request_filename | 当前请求的文件路径, 由root或alias指令与URI请求生成 |
$request_uri | 包含请求参数的原始URI, 不包含主机名, 如: ”/foo/bar.php?arg=baz”。不能修改 |
$scheme | HTTP方法(如http, https) |
$server_protocol | 请求使用的协议, 通常是HTTP/1.0或HTTP/1.1 |
$server_addr | 服务器地址, 在完成一次系统调用后可以确定这个值 |
$server_name | 服务器名称 |
$server_port | 请求到达服务器的端口号 |
$uri | 不带请求参数的当前URI, \(uri不包含主机名, 如”/foo/bar.html”。该值有可能和\)request_uri 不一致。$request_uri是浏览器发过来的值。该值是rewrite后的值。例如做了internal redirects后。 |
6.2 修复Nginx 502错误
6.2.1 upstream sent too big header while reading response header from upstream
Nginx的错误日志, 发现如下错误
2015/03/19 10:46:40 [error] 6412#0: *16436265 upstream sent too big header while reading response header from upstream, client: 192.168.101.100, server: localhost, request: "GET /search_rst.html?word=%E7%88%B1%E6%82%A0 HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: ......
看来还是Nginx的配置问题。
在Nginx配置文件的的http段, 加入下面的配置
proxy_buffer_size 128k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
重启Nginx错误依旧。再在host配置的php段加入下面配置
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
重启Nginx就不再报错了。
6.2.2 recv() failed (104: Connection reset by peer) while reading response header from upstream
此问题在于, nginx的buffer太小了, timeout太小了。在nginx配置文件目录grep buffer * -irn、grep timeout * -irn, 然后把所有的buffer加倍, 改完之后的效果如下:
client_header_buffer_size 64k;
client_body_buffer_size 20m;
large_client_header_buffers 4 64k;
gzip_buffers 16 8k;
keepalive_timeout 240;
proxy_buffer_size 64k;
proxy_buffers 4 128k;
proxy_busy_buffers_size 256k;
proxy_connect_timeout 600s;
proxy_send_timeout 1200;
proxy_read_timeout 1200;
fastcgi_connect_timeout 600;
fastcgi_send_timeout 600;
fastcgi_read_timeout 600;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 128k;
fastcgi_busy_buffers_size 256k;
同步修改配置php-fpm.conf
#php程序执行时间超过request_terminate_timeout的值, 然后php-fpm进程立即退出,
此时nginx得不到php-fpm进程的正确结果, 响应502
request_terminate_timeout = 0
#增加进程数
pm.max_requests = 500