nginx核心配置
大约 16 分钟
代理服务器:
- Nginx 的特点:高并发 / 低消耗 / 热部署 / 高可用 / 高扩展
- 正向代理:隐藏 / 翻墙 / 提速 / 缓存 / 授权
- 反向代理:保护隐藏 / 分布式路由 / 负载均衡 / 动静分离 / 数据缓存
快速获取 NGINX 的配置
请求定位
优先级由低到高依次是:普通匹配 < 长路径匹配 < 正则匹配 < 短路匹配 < 精确匹配
- 普通匹配
location /a {}
- 长路径匹配
location /a/b/c {}
- 正则匹配
# ~表示这里是正则表达式,默认匹配是区分大小写
# ~后跟上*号,表示这是不区分大小写的正则表达式
location ~/a {}
location ~*/a {}
- 短路匹配
# 以^~开头的匹配路径称为短路匹配,表示只要匹配上,就不再匹配其它的了。
location ^~/a/b {}
- 精确匹配
# 以等号(=)开头的匹配称为精确匹配,其是优先级最高的匹配。
location =/a/b {}
全局模块
- worker_processes 1
可选参数:auto,数值
Nginx 的工作进程数量,其数值一般设置为 CPU 内核数量,或内核数量的整数倍。
不过需要注意,该值不仅仅取决于 CPU 内核数量,还与硬盘数量及负载均衡模式相关。在不确定时可以指定其值为 auto。 - worker_cpu_affinity
worker 进程与具体的内核进行绑定。不过,若指定 worker_processes 的值为 auto,则无法设置 worker_cpu_affinity。
该值设置是通过二进制进行的。每个内核使用一个二进制位表示,0 代表内核关闭,1 代表内核开启。也就是说,有几个内核,就需要使用几个二进制位。
内核数量 | worker_processes | worker_cpu_affinity | 说明 |
---|---|---|---|
2 | 2 | 01 10 | 每个进程各使用一个内核 |
2 | 4 | 01 10 01 10 | 每个进程交替使用各自的内核 |
4 | 4 | 0001 0010 0100 1000 | 每个进程使用各自的一个内核 |
4 | 2 | 0101 1010 | 每个进程使用两个内核。应用于 CPU 进行大量的运算 |
- worker_rlimit_nofile
其默认值与当前 Linux 系统可以打开的最大文件描述符数量相同
# 查看Linux 系统可以打开的最大文件
ulimit -n
# 设置Linux 系统可以打开的最大文件为65535
ulimit -n 65535
events 模块
- worker_connections 1024
设置每一个 worker 进程可以并发处理的最大连接数。该值不能超过 worker_rlimit_nofile 的值。 - accept_mutex on
- on:默认值,表示当一个新连接到达时,那些没有处于工作状态的 worker 将以串行方式来处理;
- off:表示当一个新连接到达时,所有的 worker 都会被唤醒,不过只有一个 worker 能获取新连接,其它的 worker 会重新进入阻塞状态,这就是“惊群”现象。
- accept_mutex_delay 500ms
设置队首 worker 会尝试获取互斥锁的时间间隔。默认值为 500 毫秒。 - multi_accept on
- off:系统会逐个拿出新连接按照负载均衡策略,将其分配给当前处理连接个数最少的 worker。
- on:系统会实时的统计出各个 worker 当前正在处理的连接个数,然后会按照“缺编”最多的 worker 的“缺编”数量,一次性将这么多的新连接分配给该 worker。
- use epoll
设置 worker 与客户端连接的处理方式。Nginx 会自动选择适合当前系统的最高效的方式。
当然,也可以使用 use 指令明确指定所要使用的连接处理方式。user 的取值有以下几种
user 的取值有以下几种:select | poll | epoll | rtsig | kqueue | /dev/poll
select:数组
poll:链表,可处理高并发
epoll:默认使用
kqueue:应用在 BSD 系统上的 epoll。
/dev/poll:UNIX 系统上使用的 poll。
:::
http 模块
- sendfile on
设置为 on 则开启 Linux 系统的零拷贝机制,否则不启用零拷贝。
当然,开启后是否起作用,要看所使用的系统版本。CentOS6 及其以上版本支持 sendfile 零拷贝。 - tcp_nopush on
- on:以单独的数据包形式发送 Nginx 的响应头信息,而真正的响应体数据会再以数据包的形式发送,这个数据包中就不再包含响应头信息了。
- off:默认值,响应头信息包含在每一个响应体数据包中。
- tcp_nodelay on
- on:不设置数据发送缓存,即不推迟发送,适合于传输小数据,无需缓存。
- off:开启发送缓存。若传输的数据是图片等大数据量文件,则建议设置为 off。
- keepalive_timeout 60
设置客户端与 Nginx 间所建立的长连接的生命超时时间,时间到达,则连接将自动关闭。单位秒。 - keepalive_requests 10000
设置一个长连接最多可以发送的请求数。该值需要在真实环境下测试。 - client_body_timeout 10
设置客户端获取 Nginx 响应的超时时限,即一个请求从客户端发出到接收到 Nginx 的响应的最长时间间隔。若超时,则认为本次请求失败。
nginx 缓存配置
Nginx 具有很强大的缓存功能,可以对请求的 response 进行缓存,起到类似 CDN 的作用,甚至有比 CDN 更强大的功能。同时,Nginx 缓存还可以用来“数据托底”,即当后台 web 服务器挂掉的时候,Nginx 可以直接将缓存中的托底数据返回给用户。此功能就是 Nginx 实现“服务降级”的体现。 Nginx 缓存功能的配置由两部分构成:全局定义与局部定义。
http{}模块的缓存全局定义
proxy_cache_path
用于指定 Nginx 缓存的存放路径及相关配置。proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=mycache:10m inactive=2h max_size=5g use_temp_path=off; proxy_cache_path 缓存文件路径 levels 设置缓存文件目录层次;levels=1:2 表示两级目录 keys_zone 设置缓存名字和共享内存大小 inactive 在指定时间内没人访问则被删除 max_size 最大缓存空间,如果缓存空间满,默认覆盖掉缓存时间最长的资源。 use_temp_path=off 直接写入到缓存文件 ```
proxy_temp_path
指定 Nginx 缓存的临时存放目录。若 proxy_cache_path 中的 use_temp_path 设置为了 off,则该属性可以不指定。
location{}模块的缓存局部定义
- proxy_cache mycache
指定用于存放缓存 key 内存区域名称。其值为 http{}模块中 proxy_cache_path 中的 keys_zone 的值。 - proxy_cache_key $host$request_uri$arg_age
指定 Nginx 生成的缓存的 key 的组成。 - proxy_cache_bypass $arg_age
指定是否越过缓存。 - proxy_cache_methods GET HEAD
指定客户端请求的哪些提交方法将被缓存,默认为 GET 与 HEAD,但不缓存 POST。 - proxy_no_cache $aaa $bbb $ccc
指定对本次请求是否不做缓存。只要有一个不为 0,就不对该请求结果缓存。 - proxy_cache_purge $ddd $eee $fff
指定是否清除缓存 key。 - proxy_cache_lock on
指定是否采用互斥方式回源。 - proxy_cache_lock_timeout 5s
指定再次生成回源互斥锁的时限。 - proxy_cache_valid 5s
对指定的 HTTP 状态码的响应数据进行缓存,并指定缓存时间。默认指定的状态码为 200,301,302。 - proxy_cache_use_stale http_404 http_500
设置启用托底缓存的条件。而一旦这里指定了相应的状态码,则前面 proxy_cache_calid 中指定的相应状态码所生成的缓存就变为了“托底缓存”。 - expires 3m
为请求的静态资源开启浏览器端的缓存。
- proxy_cache mycache
http{
# 设置缓存的目录,并且内存中缓存区名为hot_cache,大小为128m,
# 三天未被访问过的缓存自动清楚,磁盘中缓存的最大容量为2GB。
proxy_cache_path /soft/nginx/cache levels=1:2 keys_zone=hot_cache:128m inactive=3d max_size=2g;
server{
location / {
# 使用名为nginx_cache的缓存空间
proxy_cache hot_cache;
# 对于200、206、304、301、302状态码的数据缓存1天
proxy_cache_valid 200 206 304 301 302 1d;
# 对于其他状态的数据缓存30分钟
proxy_cache_valid any 30m;
# 定义生成缓存键的规则(请求的url+参数作为key)
proxy_cache_key $host$uri$is_args$args;
# 资源至少被重复访问三次后再加入缓存
proxy_cache_min_uses 3;
# 出现重复请求时,只让一个去后端读数据,其他的从缓存中读取
proxy_cache_lock on;
# 上面的锁超时时间为3s,超过3s未获取数据,其他请求直接去后端
proxy_cache_lock_timeout 3s;
# 对于请求参数或cookie中声明了不缓存的数据,不再加入缓存
proxy_no_cache $cookie_nocache $arg_nocache $arg_comment;
# 在响应头中添加一个缓存是否命中的状态(便于调试)
add_header Cache-status $upstream_cache_status;
}
}
}
缓存清理
当缓存过多时,如果不及时清理会导致磁盘空间被“吃光”,因此我们需要一套完善的缓存清理机制去删除缓存,在之前的 proxy_cache_path 参数中有 purger 相关的选项,开启后可以帮我们自动清理缓存,但遗憾的是:purger 系列参数只有商业版的 NginxPlus 才能使用,因此需要付费才可使用。
不过天无绝人之路,我们可以通过强大的第三方模块 ngx_cache_purge 来替代,先来安装一下该插件:
mkdir -p ~/app/cache_purge && cd ~/app/cache_purge
wget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz
tar -xvzf 2.3.tar.gz
cd ~/app/nginx
./configure --prefix=/user/local/nginx/ --add-module=~/app/cache_purge/ngx_cache_purge-2.3/
make
第三方缓存清除模块 ngx_cache_purge 就安装完成了,接下来稍微修改一下 nginx.conf 配置,再添加一条 location 规则:
location ~ /purge(/.*) {
# 配置可以执行清除操作的IP(线上可以配置成内网机器)
# allow 127.0.0.1; # 代表本机
allow all; # 代表允许任意IP清除缓存
proxy_cache_purge $host$1$is_args$args;
}
然后再重启 Nginx,接下来即可通过http:127.0.0.1:80//xxx/purge/xx的方式清除缓存。
Nginx 变量
- 自定义变量
set $aaa "hello";
set $bbb 0;
- 内置变量
$args 请求中的参数;
$binary_remote_addr 远程地址的二进制表示
$body_bytes_sent 已发送的消息体字节数
$content_length HTTP 请求信息里的"Content-Length"
$content_type 请求信息里的"Content-Type"
$document_root 针对当前请求的根路径设置值
$document_uri 与$uri 相同
$host 请求信息中的"Host",如果请求中没有 Host 行,则等于设置的服务器名;
$http_cookie cookie 信息
$http_referer 来源地址
$http_user_agent 客户端代理信息
$http_via 最后一个访问服务器的 Ip 地址
$http_x_forwarded_for 相当于网络访问路径。
$limit_rate 对连接速率的限制
$remote_addr 客户端地址
$remote_port 客户端端口号
$remote_user 客户端用户名,认证用
$request 用户请求信息
$request_body 用户请求主体
$request_body_file 发往后端的本地文件名称
$request_filename 当前请求的文件路径名
$request_method 请求的方法,比如"GET"、"POST"等
$request_uri 请求的 URI,带参数
$server_addr 服务器地址,如果没有用 listen 指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费)
$server_name 请求到达的服务器名
$server_port 请求到达的服务器端口号
$server_protocol 请求的协议版本,"HTTP/1.0"或"HTTP/1.1"
$uri 请求的 URI,可能和最初的值有不同,比如经过重定向之类的
反向代理
- client_max_body_size 100k;
Nginx 允许客户端请求的单文件最大大小,单位字节。 - client_body_buffer_size 80k;
Nginx 为客户端请求设置的缓存大小。 - proxy_buffering on
开启从后端被代理服务器的响应内容缓冲区。默认值 on。 - proxy_buffers 4 8k;
该指令用于设置缓冲区的数量与大小。从被代理的后端服务器取得的响应内容,会缓 存到这里。 - proxy_busy_buffers_size 16k;
高负荷下缓存大小,其默认值为一般为单个 proxy_buffers 的 2 倍。 - proxy_connect_timeout 60s;
Nginx 跟后端服务器连接超时时间。默认 60 秒。 - proxy_read_timeout 60s;
Nginx 发出请求后等待后端服务器响应的最长时限。默认 60 秒。
负载均衡
- 轮询
默认的负载均衡策略,其是按照各个主机的权重比例依次进行请求分配的。
upstream backserver {
server 192.16.18.101 weight=1 fail_timeout=20 max_fails=3;
server 192.16.18.102 weight=2 fail_timeout=20 max_fails=3;
server 192.16.18.103 backup;
server 192.16.18.104 down;
}
# backup:表示当前服务器为备用服务器。
# down:表示当前服务器永久停机。
# fail_timeout:表示当前主机被 Nginx 认定为停机的最长失联时间,默认为 10 秒。常与max_fails 联合使用。
# max_fails:表示在 fail_timeout 时间内最多允许的失败次数。
- ip_hash
指定负载均衡器按照基于客户端 IP 的分配方式
upstream backserver {
ip_hash;
server 192.16.18.101 weight=1 fail_timeout=20 max_fails=3;
server 192.16.18.102 weight=2 fail_timeout=20 max_fails=3;
}
# 对于该策略需要注意以下几点:
# 在 nginx1.3.1 版本之前,该策略中不能指定 weight 属性。
# 该策略不能与 backup 同时使用。
# 此策略适合有状态服务,比如 session。
# 当有服务器宕机,必须手动指定 down 属性,否则请求仍是会落到该服务器。
- least_conn
把请求转发给连接数最少的服务器
upstream backserver {
least_conn;
server 192.16.18.101 weight=1 fail_timeout=20 max_fails=3;
server 192.16.18.102 weight=2 fail_timeout=20 max_fails=3;
}
- fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
upstream backend {
fair;
server localhost:8080;
server localhost:8081;
}
- url_hash(第三方)
按访问 url 的 hash 结果来分配请求,使每个 url 定向到同一个后端服务器,后端服务器为缓存时比较有效。
upstream backend {
hash $request_uri;
hash_method crc32;
server localhost:8080;
server localhost:8081;
}
虚拟主机配置
upstreams.conf
cat >> /usr/local/software/nginx/conf/upstreams.conf <<EOF
upstream www.68.com {
server tomcatOS:8081 weight=1;
server tomcatOS:8082 weight=1;
}
upstream bj.68.com {
server tomcatOS:8083 weight=1;
server tomcatOS:8084 weight=1;
}
upstream sh.68.com {
server tomcatOS:8085 weight=1;
server tomcatOS:8086 weight=1;
}
EOF
vhosts.conf
cat >> /usr/local/software/nginx/conf/vhosts.conf <<EOF
server {
listen 80;
server_name www.68.com;
location / {
proxy_pass http://www.68.com
}
}
server {
listen 80;
server_name bj.68.com;
location / {
proxy_pass http://bj.68.com
}
}
server {
listen 80;
server_name sh.68.com;
location / {
proxy_pass http://sh.68.com
}
}
EOF
worker_processes 1;
events {
worker_connections 1024;
}
http {
include /usr/local/software/nginx/conf/upstreams.conf;
include /usr/local/software/nginx/conf/vhosts.conf;
server {
...
}
}
Nginx 性能调优
## 零拷贝(Zero Copy)
- 传统的拷贝方式
- Gather Copy DMA 零拷贝方式
- mmap 零拷贝
## 多路复用器 select|poll|epoll
- select<br/>
select 多路复用器是采用轮询的方式,一直在轮询所有的相关内核进程,查看它们的进程状态。若已经就绪,则马上将该内核进程放入到就绪队列。否则,继续查看下一个内核进程状态。在处理内核进程事务之前,app 进程首先会从内核空间中将用户连接请求相关数据复制到用户空间。<br/>
该多路复用器的缺陷有以下几点:
- 对所有内核进程采用轮询方式效率会很低。因为对于大多数情况下,内核进程都不属于
就绪状态,只有少部分才会是就绪态。所以这种轮询结果大多数都是无意义的。
- 由于就绪队列底层由数组实现,所以其所能处理的内核进程数量是有限制的,即其能够
处理的最大并发连接数量是有限制的。
- 从内核空间到用户空间的复制,系统开销大。
- poll<br/>
poll 多路复用器的工作原理与 select 几乎相同,不同的是,由于其就绪队列由链表实现,所以,其对于要处理的内核进程数量理论上是没有限制的,即其能够处理的最大并发连接数量是没有限制的(当然,要受限于当前系统中进程可以打开的最大文件描述符数 ulimit,后面会讲到)。
- epoll<br/>
epoll 多路复用是对 select 与 poll 的增强与改进。其不再采用轮询方式了,而是采用回调方式实现对内核进程状态的获取:一旦内核进程就绪,其就会回调 epoll 多路复用器,进入到多路复用器的就绪队列(由链表实现)。所以 epoll 多路复用模型也称为 epoll 事件驱动模型。<br/>
另外,应用程序所使用的数据,也不再从内核空间复制到用户空间了,而是使用 mmap零拷贝机制,大大降低了系统开销。<br/>
当内核进程就绪信息通知了 epoll 多路复用器后,多路复用器就会马上对其进行处理,将其马上存放到就绪队列吗?不是的。根据处理方式的不同,可以分为两种处理模式:LT模式与 ET 模式。
- LT (Level Triggered)<br/>
水平触发模式。<br/>即只要内核进程的就绪通知由于某种原因暂时没有被 epoll 处理,则该内核进程就会定时将其就绪信息通知 epoll。直到 epoll 将其写入到就绪队列,或由于某种原因该内核进程又不再就绪而不再通知。其支持两种通讯方式:BIO 与
NIO。
- ET (Edge Triggered)<br/>
边缘触发模式。其仅支持 NIO 的通讯方式。<br/>当内核进程的就绪信息仅会通知一次 epoll,无论 epoll 是否处理该通知。明显该方式的效率要高于 LT 模式,但其有可能会出现就绪通知被忽视的情况,即连接请求丢失的情况。
## Nginx 的并发处理机制
一般情况下并发处理机制有三种:多进程、多线程,与异步机制。<br/>
Nginx 对于并发的处理同时采用了三种机制。当然,其异步机制使用的是异步非阻塞方式。<br/>
我们知道 Nginx 的进程分为两类:master 进程与 worker 进程。每个 master 进程可以生成多个 worker 进程,所以其是多进程的。每个 worker 进程可以同时处理多个用户请求,每个用户请求会由一个线程来处理,所以其是多线程的。<br/>
那么,如何解释其“异步非阻塞”并发处理机制呢?<br/>
worker 进程采用的就是 epoll 多路复用机制来对后端服务器进行处理的。当后端服务器返回结果后,后端服务器就会回调 epoll 多路复用器,由多路复用器对相应的 worker 进程进行通知。此时,worker 进程就会挂起当前正在处理的事务,拿 IO 返回结果去响应客户端请求。响应完毕后,会再继续执行挂起的事务。这个过程就是“异步非阻塞”的。
跨域问题
location / {
# 允许跨域的请求,可以自定义变量$http_origin,*表示所有
add_header 'Access-Control-Allow-Origin' *;
# 允许携带cookie请求
add_header 'Access-Control-Allow-Credentials' 'true';
# 允许跨域请求的方法:GET,POST,OPTIONS,PUT
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT';
# 允许请求时携带的头部信息,*表示所有
add_header 'Access-Control-Allow-Headers' *;
# 允许发送按段获取资源的请求
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
# 一定要有!!!否则Post请求无法进行跨域!
# 在发送Post跨域请求前,会以Options方式发送预检请求,服务器接受时才会正式请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
# 对于Options方式的请求返回204,表示接受跨域请求
return 204;
}
}