总字符数: 17.17K

代码: 8.84K, 文本: 3.25K

预计阅读时间: 53 分钟

Nginx 实战

我始终认为,各种开发工具的配置还是结合实战来讲述,会让人更易理解.

Http 反向代理

我们先实现一个小目标:不考虑复杂的配置,仅仅是完成一个 http 反向代理.

nginx.conf 配置文件如下:

注:conf/nginx.conf 是 nginx 的默认配置文件.你也可以使用 nginx -c 指定你的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#运行用户
#user nobody;

#启动进程,通常设置成和cpu的数量相等
worker_processes 1;

#全局错误日志
error_log logs/error.log;
error_log logs/notice.log notice;
error_log logs/info.log info;

#PID文件,记录当前启动的nginx的进程ID
pid logs/nginx.pid;

# 工作模式及连接数上限
events {
worker_connections 1024; #单个后台worker process进程的最大并发链接数
}

#设定http服务器,利用它的反向代理功能提供负载均衡支持
http {
#设定mime类型(邮件支持类型),类型由mime.types文件定义
include conf/mime.types;
default_type application/octet-stream;

#设定日志
log_format main '[$remote_addr] - [$remote_user] [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log logs/access.log main;
rewrite_log on;

#sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件,对于普通应用,
#必须设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为 off,以平衡磁盘与网络I/O处理速度,降低系统的uptime.
sendfile on;
#tcp_nopush on;

#连接超时时间
keepalive_timeout 120;
tcp_nodelay on;

#gzip压缩开关
#gzip on;

#设定实际的服务器列表
upstream zp_server1{
server 127.0.0.1:8089;
}

#HTTP服务器
server {
#监听80端口,80端口是知名端口号,用于HTTP协议
listen 80;

#定义使用www.xx.com访问
server_name www.helloworld.com;

#首页
index index.html

#指向webapp的目录
root html;

#编码格式
charset utf-8;

#代理配置参数
proxy_connect_timeout 180;
proxy_send_timeout 180;
proxy_read_timeout 180;
proxy_set_header Host $host;
proxy_set_header X-Forwarder-For $remote_addr;

#反向代理的路径(和upstream绑定),location 后面设置映射的路径
location / {
proxy_pass http://zp_server1;
}

#静态文件,nginx自己处理
location ~ ^/(images|javascript|js|css|flash|media|static)/ {
root html/views;
#过期30天,静态文件不怎么更新,过期可以设大一点,如果频繁更新,则可以设置得小一点.
expires 30d;
}

#设定查看Nginx状态的地址
location /NginxStatus {
stub_status on;
access_log on;
auth_basic "NginxStatus";
auth_basic_user_file conf/htpasswd;
}

#禁止访问 .htxxx 文件
location ~ /\.ht {
deny all;
}

#错误处理页面(可选择性配置)
#error_page 404 /404.html;
#error_page 500 502 503 504 /50x.html;
#location = /50x.html {
# root html;
#}
}
}

好了,让我们来试试吧:

  1. 启动 webapp,注意启动绑定的端口要和 nginx 中的 upstream 设置的端口保持一致.
  2. 更改 host:在 C:\Windows\System32\drivers\etc 目录下的 host 文件中添加一条 DNS 记录
1
127.0.0.1 www.helloworld.com
  1. 启动前文中 startup.bat 的命令
  2. 在浏览器中访问 www.helloworld.com,不出意外,已经可以访问了.

Https 反向代理

一些对安全性要求比较高的站点,可能会使用 HTTPS(一种使用 ssl 通信标准的安全 HTTP 协议).

这里不科普 HTTP 协议和 SSL 标准.但是,使用 nginx 配置 https 需要知道几点:

  • HTTPS 的固定端口号是 443,不同于 HTTP 的 80 端口
  • SSL 标准需要引入安全证书,所以在 nginx.conf 中你需要指定证书和它对应的 key

其他和 http 反向代理基本一样,只是在 Server 部分配置有些不同.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#HTTP服务器
server {
#监听443端口.443为知名端口号,主要用于HTTPS协议
listen 443 ssl;

#定义使用www.xx.com访问
server_name www.helloworld.com;

#ssl证书文件位置(常见证书文件格式为:crt/pem)
ssl_certificate cert.pem;
#ssl证书key位置
ssl_certificate_key cert.key;

#ssl配置参数(选择性配置)
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
#数字签名,此处使用MD5
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

location / {
root /root;
index index.html index.htm;
}
}

负载均衡

前面的例子中,代理仅仅指向一个服务器.

但是,网站在实际运营过程中,大部分都是以集群的方式运行,这时需要使用负载均衡来分流.

nginx 也可以实现简单的负载均衡功能.

假设这样一个应用场景:将应用部署在 192.168.1.11:80、192.168.1.12:80、192.168.1.13:80 三台 linux 环境的服务器上.网站域名叫 www.helloworld.com ,公网 IP 为 192.168.1.11.在公网 IP 所在的服务器上部署 nginx,对所有请求做负载均衡处理(下面例子中使用的是加权轮询策略).

nginx.conf 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
http {
#设定mime类型,类型由mime.type文件定义
include /etc/nginx/mime.types;
default_type application/octet-stream;
#设定日志格式
access_log /var/log/nginx/access.log;

#设定负载均衡的服务器列表
upstream load_balance_server {
#weigth参数表示权值,权值越高被分配到的几率越大
server 192.168.1.11:80 weight=5;
server 192.168.1.12:80 weight=1;
server 192.168.1.13:80 weight=6;
}

#HTTP服务器
server {
#侦听80端口
listen 80;

#定义使用www.xx.com访问
server_name www.helloworld.com;

#对所有请求进行负载均衡请求
location / {
root /root; #定义服务器的默认网站根目录位置
index index.html index.htm; #定义首页索引文件的名称
proxy_pass http://load_balance_server ;#请求转向load_balance_server 定义的服务器列表

#以下是一些反向代理的配置(可选择性配置)
#proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header X-Forwarded-For $remote_addr;
proxy_connect_timeout 90; #nginx跟后端服务器连接超时时间(代理连接超时)
proxy_send_timeout 90; #后端服务器数据回传时间(代理发送超时)
proxy_read_timeout 90; #连接成功后,后端服务器响应时间(代理接收超时)
proxy_buffer_size 4k; #设置代理服务器(nginx)保存用户头信息的缓冲区大小
proxy_buffers 4 32k; #proxy_buffers缓冲区,网页平均在32k以下的话,这样设置
proxy_busy_buffers_size 64k; #高负荷下缓冲大小(proxy_buffers*2)
proxy_temp_file_write_size 64k; #设定缓存文件夹大小,大于这个值,将从upstream服务器传

client_max_body_size 10m; #允许客户端请求的最大单文件字节数
client_body_buffer_size 128k; #缓冲区代理缓冲用户端请求的最大字节数
}
}
}

负载均衡策略

Nginx 提供了多种负载均衡策略,让我们来一一了解一下:

负载均衡策略在各种分布式系统中基本上原理一致,对于原理有兴趣,不妨参考 负载均衡

轮询

1
2
3
4
5
6
upstream bck_testing_01 {
# 默认所有服务器权重为 1
server 192.168.250.220:8080
server 192.168.250.221:8080
server 192.168.250.222:8080
}

加权轮询

1
2
3
4
5
upstream bck_testing_01 {
server 192.168.250.220:8080 weight=3
server 192.168.250.221:8080 # default weight=1
server 192.168.250.222:8080 # default weight=1
}

最少连接

1
2
3
4
5
6
7
8
upstream bck_testing_01 {
least_conn;

# with default weight for all (weight=1)
server 192.168.250.220:8080
server 192.168.250.221:8080
server 192.168.250.222:8080
}

加权最少连接

1
2
3
4
5
6
7
upstream bck_testing_01 {
least_conn;

server 192.168.250.220:8080 weight=3
server 192.168.250.221:8080 # default weight=1
server 192.168.250.222:8080 # default weight=1
}

IP Hash

1
2
3
4
5
6
7
8
9
10
upstream bck_testing_01 {

ip_hash;

# with default weight for all (weight=1)
server 192.168.250.220:8080
server 192.168.250.221:8080
server 192.168.250.222:8080

}

普通 Hash

1
2
3
4
5
6
7
8
9
10
upstream bck_testing_01 {

hash $request_uri;

# with default weight for all (weight=1)
server 192.168.250.220:8080
server 192.168.250.221:8080
server 192.168.250.222:8080

}

网站有多个 webapp 的配置

当一个网站功能越来越丰富时,往往需要将一些功能相对独立的模块剥离出来,独立维护.这样的话,通常,会有多个 webapp.

举个例子:假如 www.helloworld.com 站点有好几个 webapp,finance(金融)、product(产品)、admin(用户中心).访问这些应用的方式通过上下文(context)来进行区分:

www.helloworld.com/finance/

www.helloworld.com/product/

www.helloworld.com/admin/

我们知道,http 的默认端口号是 80,如果在一台服务器上同时启动这 3 个 webapp 应用,都用 80 端口,肯定是不成的.所以,这三个应用需要分别绑定不同的端口号.

那么,问题来了,用户在实际访问 www.helloworld.com 站点时,访问不同 webapp,总不会还带着对应的端口号去访问吧.所以,你再次需要用到反向代理来做处理.

配置也不难,来看看怎么做吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
http {
#此处省略一些基本配置

upstream product_server{
server www.helloworld.com:8081;
}

upstream admin_server{
server www.helloworld.com:8082;
}

upstream finance_server{
server www.helloworld.com:8083;
}

server {
#此处省略一些基本配置
#默认指向product的server
location / {
proxy_pass http://product_server;
}

location /product/{
proxy_pass http://product_server;
}

location /admin/ {
proxy_pass http://admin_server;
}

location /finance/ {
proxy_pass http://finance_server;
}
}
}

搭建文件服务器

有时候,团队需要归档一些数据或资料,那么文件服务器必不可少.使用 Nginx 可以非常快速便捷的搭建一个简易的文件服务.

Nginx 中的配置要点:

  • 将 autoindex 开启可以显示目录,默认不开启.
  • 将 autoindex_exact_size 开启可以显示文件的大小.
  • 将 autoindex_localtime 开启可以显示文件的修改时间.
  • root 用来设置开放为文件服务的根路径.
  • charset 设置为 charset utf-8,gbk;,可以避免中文乱码问题(windows 服务器下设置后,依然乱码,本人暂时没有找到解决方法).

一个最简化的配置如下:

1
2
3
4
5
6
7
8
9
10
autoindex on;# 显示目录
autoindex_exact_size on;# 显示文件大小
autoindex_localtime on;# 显示文件时间

server {
charset utf-8,gbk; # windows 服务器下设置后,依然乱码,暂时无解
listen 9050;
server_name _;
root /share/fs;
}

解决跨域

web 领域开发中,经常采用前后端分离模式.这种模式下,前端和后端分别是独立的 web 应用程序,例如:后端是 Java 程序,前端是 React 或 Vue 应用.

各自独立的 web app 在互相访问时,势必存在跨域问题.解决跨域问题一般有两种思路:

  1. CORS

在后端服务器设置 HTTP 响应头,把你需要允许访问的域名加入 Access-Control-Allow-Origin 中.

  1. jsonp

把后端根据请求,构造 json 数据,并返回,前端用 jsonp 跨域.

这两种思路,本文不展开讨论.

需要说明的是,nginx 根据第一种思路,也提供了一种解决跨域的解决方案.

举例:www.helloworld.com 网站是由一个前端 app ,一个后端 app 组成的.前端端口号为 9000, 后端端口号为 8080.

前端和后端如果使用 http 进行交互时,请求会被拒绝,因为存在跨域问题.来看看,nginx 是怎么解决的吧:

首先,在 enable-cors.conf 文件中设置 cors :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# allow origin list
set $ACAO '*';

# set single origin
if ($http_origin ~* (www.helloworld.com)$) {
set $ACAO $http_origin;
}

if ($cors = "trueget") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}

if ($request_method = 'OPTIONS') {
set $cors "${cors}options";
}

if ($request_method = 'GET') {
set $cors "${cors}get";
}

if ($request_method = 'POST') {
set $cors "${cors}post";
}

接下来,在你的服务器中 include enable-cors.conf 来引入跨域配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# ----------------------------------------------------
# 此文件为项目 nginx 配置片段
# 可以直接在 nginx config 中 include(推荐)
# 或者 copy 到现有 nginx 中,自行配置
# www.helloworld.com 域名需配合 dns hosts 进行配置
# 其中,api 开启了 cors,需配合本目录下另一份配置文件
# ----------------------------------------------------
upstream front_server{
server www.helloworld.com:9000;
}
upstream api_server{
server www.helloworld.com:8080;
}

server {
listen 80;
server_name www.helloworld.com;

location ~ ^/api/ {
include enable-cors.conf;
proxy_pass http://api_server;
rewrite "^/api/(.*)$" /$1 break;
}

location ~ ^/ {
proxy_pass http://front_server;
}
}

到此,就完成了.

Nginx问题集

Nginx 出现大量 TIME_WAIT

检测TIME_WAIT状态的语句

1
2
3
4
5
6
$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'  
SYN_RECV 7
ESTABLISHED 756
FIN_WAIT1 21
SYN_SENT 3
TIME_WAIT 2000

状态解析:

  • CLOSED - 无连接是活动的或正在进行
  • LISTEN - 服务器在等待进入呼叫
  • SYN_RECV - 一个连接请求已经到达,等待确认
  • SYN_SENT - 应用已经开始,打开一个连接
  • ESTABLISHED - 正常数据传输状态
  • FIN_WAIT1 - 应用说它已经完成
  • FIN_WAIT2 - 另一边已同意释放
  • ITMED_WAIT - 等待所有分组死掉
  • CLOSING - 两边同时尝试关闭
  • TIME_WAIT - 另一边已初始化一个释放
  • LAST_ACK - 等待所有分组死掉

解决方法

执行 vim /etc/sysctl.conf,并添加下面字段

1
2
3
4
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30

执行 /sbin/sysctl -p 让修改生效.

上传文件大小限制

问题现象

显示错误信息:413 Request Entity Too Large.

意思是请求的内容过大,浏览器不能正确显示.常见的情况是发送 POST 请求来上传大文件.

解决方法

  • 可以在 http 模块中设置:client_max_body_size 20m;
  • 可以在 server 模块中设置:client_max_body_size 20m;
  • 可以在 location 模块中设置:client_max_body_size 20m;

三者区别是:

  • 如果文大小限制设置在 http 模块中,则对所有 Nginx 收到的请求.
  • 如果文大小限制设置在 server 模块中,则只对该 server 收到的请求生效.
  • 如果文大小限制设置在 location 模块中,则只对匹配了 location 路由规则的请求生效.

请求时间限制

问题现象

请求时间较长,链接被重置页面刷新.常见的情况是:上传、下载大文件.

解决方法

修改超时时间

Nginx编译参数详解

理论上来说 Nginx 没有必选参数,所有参数都是可选的,基本上只添加--prefix参数即可,但不选任何参数就毫无意义了.

Nginx 的核心功能包括 HTTP 、 Mail 和 Stream ,本文将以核心功能为区分解析 Nginx 的编译参数.

可能仅介绍一些核心参数,对于非核心参数请查看 Nginx 官方文档.

基础路径类

参数 示例值 说明
--prefix=<path> /etc/nginx Nginx 安装的根路径, 所有其它路径类参数都要依赖该选项
--sbin-path=<path> /usr/sbin/nginx Nginx 二进制文件路径, 不指定则使用<prefix>/sbin/nginx
--conf-path=<path> /etc/nginx/nginx.conf 配置文件默认路径, 不指定则需要在运行时附带-c参数
--error-log-path=<path> /var/log/nginx/error.log 错误日志路径, 可以被配置文件中的配置项覆盖掉
--pid-path=<path> /var/run/nginx.pid Nginx 主进程 PID 写入位置
--user=<user> nginx Worker 进程运行的用户
--group=<group> nginx Worker 进程运行的组
--modules-path=<path> /usr/lib/nginx/modules Nginx 模块路径
--lock-path=<path> /var/run/nginx.lock Lock文件位置
--http-log-path /var/log/nginx/access.log HTTP 访问日志位置

HTTP 功能类

参数 说明
--with-http_ssl_module 启用SSL支持
--with-http_v2_module 启用HTTP2支持
--with-http_realip_module 当Nginx服务器在反向代理后面的时候, 该模块可让 Nginx 知晓真正的用户IP
--with-http_addition_module 过滤相应前后的文本
--with-http_image_filter_module 转换 JPEG、GIF、PNG 和 WebP 格式图像 该模块可被编译为动态模块
--with-http_sub_module 可以替换指定的字符串来修改响应数据
--with-http_dav_module 开启 WebDAV 协议
--with-http_flv_module 对 Flash 启用服务端支持
--with-http_mp4_module 对 MP4 文件启用服务端支持
--with-http_gunzip_module 对不支持 gzip 编码方法的客户端解压缩 Content-Encoding:gzip 的响应. 可以存储压缩数据以节省空间并降低 I/O 成本
--with-http_gzip_static_module 发送以 .gz 结尾的预压缩文件替代普通文件
--with-http_auth_request_module 基于子请求结果实现客户端授权
--with-http_random_index_module 随机选择目录中的文件作为索引文件展示
--with-http_secure_link_module 用于检查请求链接的真实性, 保护资源免受未经授权的访问, 并限制链接有效时长.
--with-http_degradation_module 用于当主机剩余内存较低时,用户请求访问, Nginx会对某些请求返回204或444的响应码
--with-http_slice_module 将请求切片并返回
--with-http_stub_status_module 提供对基本状态信息的访问的支持
--without-http 禁用 HTTP 功能
--without-http-cache 禁用 HTTP 缓存功能

邮件类

参数 说明
--with-mail 启用邮件功能 该模块可被编译为动态模块
--with-mail_ssl_module 对邮件功能启用SSL
--without-mail_pop3_module 禁用POP3
--without-mail_imap_module 禁用IMAP
--without-mail_smtp_module 禁用SMTP

其他参数

参数 说明
--with-google_perftools_module 可以使用 Google 性能工具 对 nginx 的 worker 进程进行分析
--with-compat 启用动态模块兼容性
--with-file-aio 启用异步IO
--with-threads 启用线程池功能
--with-cpu-opt=<cpu> 为特定的CPU执行编译操作 有效的值:pentium,pentiumpro,pentium3,pentium4,athlon,opteron,sparc32,sparc64,ppc64.

模块配置示例

http_random_index_module

1
2
3
location / {
random_index on;
}

http_stub_status_module

1
2
3
location = /basic_status {
stub_status;
}

其他附加模块

ngx_brotli

Brotli 是基于LZ77算法的一个现代变体、霍夫曼编码和二阶上下文建模.Google软件工程师在2015年9月发布了包含通用无损数据压缩的Brotli增强版本,特别侧重于HTTP压缩.其中的编码器被部分改写以提高压缩比,编码器和解码器都提高了速度,流式API已被改进,增加更多压缩质量级别.

与常见的通用压缩算法不同,Brotli使用一个预定义的120千字节字典.该字典包含超过13000个常用单词、短语和其他子字符串,这些来自一个文本和HTML文档的大型语料库.预定义的算法可以提升较小文件的压缩密度.

使用Brotli替换Deflate来对文本文件压缩通常可以增加20%的压缩密度,而压缩与解压缩速度则大致不变.

  1. 下载源码

    1
    2
    3
    cd /usr/local/src/
    git clone https://github.com/google/ngx_brotli
    cd ngx_brotli && git submodule update --init
  2. 重新编译 Nginx

    编译时只需要在原有的编译参数后面添加--add-module=/usr/local/src/ngx_brotli.

    1
    2
    # 示例如下:
    ./configure --prefix=/usr/share/nginx --with-http_ssl_module --add-module=/usr/local/src/ngx_brotli
  3. 检查是否安装成功

    1
    nginx -V

    输出中有ngx_brotli字样即为成功.

  4. 修改配置文件

    1
    2
    3
    4
    #Brotli Compression
    brotli on; # switch
    brotli_comp_level 6; # level 1-11
    brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;

其中brotli_comp_level值为压缩比,1 压缩比最小,处理速度最快,11 压缩比最大,传输速度快,但是处理慢,也比较消耗CPU资源.